Sunday 10 May 2009

Clojure on the Google App Engine

The Google App Engine offers a complete stack for deploying applications in the cloud. Initially, support only existed for Python, but recently support was announced for Java.

Although, the main page announces this as support for the Java Language, it's much more than that, it's support for the Java Virtual Machine. The list of languages on the JVM is huge. This means that in theory, any of these languages can now be hosted in the cloud.

So how easy is it to get Clojure going in the cloud?

Firstly, register at http://appengine.google.com and get yourself an account. Download the Java AppEngine SDK too and unpack that and get the development server up and running.

GAE is based on the Servlet 2.5 specification, so the typical directory structure looks very similar to any JSP/Servlet type application:


  • Root - Top level directory for the project
  • Root/src - Source code
  • Root/war - JSP files and HTML artefacts
  • Root/war/WEB-INF -Application configuration files
  • Root/war/WEB-INF/classes - Compiled source files
  • Root/war/WEB-INF/lib - Deployment time dependencies


As GAE is based on servlets, we need to define a simple servlet for the mandatory hello world demo! This code goes in the src directory:


(ns helloclojure.servlet
(:gen-class :extends javax.servlet.http.HttpServlet))

(defn -doGet
[_ request response]
(let [w (.getWriter response)]
(.println w "Hello world!")))


:gen-class causes Clojure to emit a class file representing this name space. The - before doGet indicates that this is a member function with three arguments (the first representing "this", unused and therefore a _ symbol). So all we do for this servlet is write "hello world" whenever any request is made.

Next we need to make a standard web.xml descriptor and put that in META-INF. This registers the servlet and specifies the mapping between a URL format and the servlet that deals with the request.



<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
<servlet>
<servlet-name>helloclojure</servlet-name>
<servlet-class>helloclojure.servlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>helloclojure</servlet-name>
<url-pattern>/helloclojure</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>


Also in the META-INF directory we need a descriptor for the GAE.



<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application></application>
<version>1</version>
</appengine-web-app>


That's all the scaffolding you need. Finally, we need some way to build and deploy this. Thankfully, someone has already looked at this, so I took their build script and made a few modifications (remove the test target for example) in the name of simplification.

The net result is the following Ant script.


<project name="helloclojure" basedir="." default="compile">

<property environment="env" />
<property name="sdk.dir" location="/home/jfoster/appengine-java-sdk-1.2.0" />
<property name="classes.dir" value="war/WEB-INF/classes" />
<property name="lib.dir" value="war/WEB-INF/lib" />
<property name="src.dir" value="src/" />

<import file="${sdk.dir}/config/user/ant-macros.xml"/>

<path id="project.classpath">
<pathelement path="${classes.dir}" />
<pathelement path="${src.dir}/helloworld" />
<fileset dir="${lib.dir}">
<include name="**/*.jar" />
</fileset>
<fileset dir="${sdk.dir}/lib">
<include name="**/*.jar" />
</fileset>
</path>

<target name="clean">
<delete dir="${classes.dir}" />
</target>

<target name="init">
<mkdir dir="${classes.dir}" />
</target>

<target name="compile" depends="clean,init">
<java classname="clojure.lang.Compile" classpathref="project.classpath" failonerror="true">
<classpath path="${src.dir}" />
<sysproperty key="clojure.compile.path" value="${classes.dir}" />
<arg value="helloclojure.servlet" />
</java>
</target>

<target name="devserver" description="run local dev appserver" depends="compile">
<dev_appserver war="war" />
</target>

<target name="deploy" description="deploy to appspot" depends="compile">
<appcfg action="update" war="war" />
</target>

</project>


All code is available on my Git Repo

4 comments:

  1. As far as I know, Clojure (as any other Lisp dialect) may emit additional sources at compile time using macros.

    If this is true (I am not a Lisp hacker, so could not say for sure), it would be interesting to see your example written in pure Clojure that emits all deployment artifacts (like web.xml) using this technique.

    What do you think?

    ReplyDelete
  2. Hi Sergey,

    Thanks for the comments!

    You definitely could do this and it's an interesting idea. It could reduce the verbosity (if you use Lisp instead of XML then you avoid the tax on repeated close tag statements).

    I'm guessing the XML for the application description would look a little like this:

    (appengine-web-app
    (:xmlns "http://appengine.google.com/ns/1.0")
    (application)
    (version 1))
    The nesting and parenthesis avoid the need for extra close tags. This would then generate the XML and build artefacts and feed the next part of the process.

    That's definitely something I might look at in more detail later. Next on my list will be looking at the persistence API on GAE.

    ReplyDelete
  3. Very helpful, thanks.
    Especially since the latest compojure throws up several errors if I use it with appengine.

    btw, I find Leiningen much easier than ant while building my clojure apps

    ReplyDelete
  4. grinnbearit - thanks for the feedback.

    Leiningen looks really good - building with Ant isn't fun. I haven't had a chance to use Leiningen for anything yet - do you have an example of it'd look for this project compared to Ant?

    ReplyDelete