Deploying a Clojure web app on Heroku

Heroku is a cloud application platform for Ruby/Rails and Node.js. However, the Cedar stack on Heroku makes it possible to deploy other types of applications. In this blog entry, I will first describe how to write a simple Clojure web app using the Ring library and the build tool Leiningen. Then I will show how to deploy this Clojure web app on Heroku, using nothing but Git. I will make a change and see how to deploy that. I will also show how to easily roll back to a previous release.

Clojure maps

A map is a data structure that associates keys to values. Maps are very important in Clojure, because they are used to store entity data, much like custom classes with fields and setters/getters are used in traditional object-oriented languages like Java. In the Java world, we have java.util.Map. They are somewhat cumbersome to work with, mostly due to the static, generic typing, but also because there is no literal map syntax, plus the fact that you can’t initialize it with elements. You must first create it, then mutate it:

import java.util.Map;
import java.util.HashMap;

Map<String,Object> map = new HashMap<String,Object>();
map.put("somekey", "somevalue");

Clojure maps, on the other hand, have a very concise literal syntax. Zero or more key-value pairs between curly braces. That’s it.

(def map {:somekey "somevalue"})

The key :somekey in the map above is a Clojure keyword. Keywords are symbolic identifiers that always evaluate to themselves, and they have a very fast equality check. For those reasons (and others, as we will see), keywords are often used, rather than strings, as keys in maps.

A Clojure map is not only a data structure, it is also a function. When a map is called with a key, it will return the corresponding value (or nil if no key was found). It can look like this when testing it from the REPL:

user> (def animal {:species :lion, :name "Leo", :age 3})
user> (animal :name)
"Leo"

In fact, keywords are functions too. When a keyword is called with a map as argument, it will look itself up in the map and return the matching value:

user> (:name animal)
"Leo"

This form is more common in idiomatic Clojure.

The Ring library

Ring is a Clojure web applications library inspired by Ruby’s Rack. Ring abstracts the HTTP request and response as maps, and expects handlers to be functions that take a map as argument and returns a map. The returned map is transformed by Ring into a HTTP response, which is sent back to the caller. This is a simple, but powerful abstraction. It means that web handlers can be written as regular functions, which can be tested in isolation. Web libraries like Compojure build on top of the Ring abstraction and provide more high-level constructs, like routing. In order to not complicate this example, however, we will stick to plain Ring.

Let’s say that we have a HTTP request that looks like this:

GET /welcome HTTP/1.1
HOST: www.sayhello.com

Ring transforms this into a Clojure map that looks like this:

{:protocol        :http
 :request-method  :get
 :uri             "/welcome"
 :server-name     "www.sayhello.com"}

Let’s further assume that we want to generate a corresponding HTTP response:

HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 11

Hello world

In order to produce that HTTP response, Ring expects this map:

{:status 200
 :headers
    {"Content-Type" "text/plain"
     "Content-Length" 11}
 :body "Hello world"}

So all we need to do is to provide a function that returns the above map, like this:

(defn app [req]
  {:status 200
   :headers
      {"Content-Type" "text/plain"
       "Content-Length" 11}
   :body "Hello world"})

Ring provides a response convenience function that returns a skeletal response map with status 200, no headers, and the given argument as body:

(use 'ring.util.response)
(defn app [req]
  (response "Hello world"))

We’ll use the response function in our example later.

Leiningen

In order to manage our dependencies, we will use the Leiningen build tool. The simplest way to install it is to download the stable release of the script and place it somewhere in the PATH, like ~/bin. Then run lein self-install:

$ lein self-install
Downloading Leiningen now...
...

$ lein version
Leiningen 1.5.2 on Java 1.6.0_24 Java HotSpot(TM) 64-Bit Server VM

Building the web application

We create a new project and go there:

$ lein new cljheroku
$ cd cljheroku

The newly created directory contains the following files:

cljheroku/
├── README
├── project.clj
├── src
│   └── cljheroku
│       └── core.clj
└── test
    └── cljheroku
        └── test
            └── core.clj

First we need to create a Procfile for Heroku:

$ echo "web: lein run -m cljheroku.core" > Procfile

This will have Leiningen start the web application by running the function called -main in the namespace cljheroku.core, ie the file src/cljheroku/core.clj. We’ll change that file to contain this:

(ns cljheroku.core
  (:use ring.util.response
        ring.adapter.jetty))

(defn app [req]
  (response "Hello World"))

(defn -main []
  (let [port (Integer/parseInt (get (System/getenv) "PORT" "8080"))]
    (run-jetty app {:port port})))

Let’s look at this code in more detail. We begin with the first chunk:

(ns cljheroku.core
  (:use ring.util.response
        ring.adapter.jetty))

The ns macro will create a namespace cljheroku.core and load all functions in the namespaces ring.util.response and ring.adapter.jetty. Since we used :use and not :require, these functions can be referenced by name, without any prefix or namespace qualifier. This enables us to write (response ...) instead of (ring.util.response/response ...).

Then we define our Ring handler called app.

(defn app [req]
  (response "Hello World"))

As you can see, it’s just a regular function that takes one argument: a HTTP request map, and returns the result of the response function: a HTTP response map. If we wanted to set the Content-Type header, we could use the content-type function, another function from ring.util.response. Instead of (response "Hello world"), we would pass the response through the content-type function: (content-type (response "Hello world") "text/plain"). But here we will just use response.

Finally we specify a -main function.

(defn -main []
  (let [port (Integer/parseInt (get (System/getenv) "PORT" "8080"))]
    (run-jetty app {:port port})))

It will serve the same purpose as the Java main  method, namely provide an entry point from the outside world. It gets the value of the PORT environment variable, or uses 8080 as default. It can be enlightening to analyze that part in detail. (System/getenv) is simply Clojure’s way of calling Java’s static method System.getenv() , which returns a java.util.Map. The get function takes a map (yes, it works with Java maps too), a key and a default value, and returns the matching value. So, (get (System/getenv) “PORT” “8080”)  will get the PORT variable from the environment, and if there is no PORT set there, it will return “8080”. The resulting string value of the port is parsed into an integer, and is stored as the local variable port. This value is then passed in to the Jetty adapter as part of a configuration map. All this in two lines. If you’re not used to Clojure’s lack of ceremony, you might find it overwhelming. I personally find it refreshingly to-the-point.

OK, that was the source code for our handler. We now need to adjust the project.clj  file slightly, and provide a dependency to Ring. We’ll also add an exclusion of a duplicate dependency that Jetty has. Here is the resulting project.clj :

(defproject cljheroku "1.0.0-SNAPSHOT"
  :description "Example Ring app running on Heroku"
  :dependencies [[org.clojure/clojure "1.2.1"]
                 [ring/ring-core "0.3.8"]
                 [ring/ring-jetty-adapter "0.3.8"]]
  :exclusions [org.mortbay.jetty/servlet-api])

Before we do anything else, though, we should add this baby to Git:

$ git init
Initialized empty Git repository in /Users/john/Source/cljheroku/.git/

$ git add .

$ git commit -m "Initial commit"

We should probably push this to a maintained repository somewhere, but for our purposes, we have now stored this in Git.

Testing locally

Before we test it, we will ask Leiningen to retrieve the dependencies:

$ lein deps

The lib directory now contains a list of jars that we need. Leiningen will make sure they all get on the classpath without you ever having to see it. You can ask Leiningen for the classpath, though, should you really like to see it:

$ lein classpath

You can also have Leiningen create a pom file for you, which can be handy if you need to look at the dependency:tree, import the project into an IDE that only knows Maven; things like that:

$ lein pom

Anyway, we can now start this web application using the same command as we placed in the Procfile for Heroku. It will start Clojure with the correct classpath, find and make available the given namespace (cljheroku.core), and call its -main function with any given arguments (none, in our case):

$ lein run -m cljheroku.core
2011-06-12 18:41:24.927:INFO::Logging to STDERR via org.mortbay.log.StdErrLog
2011-06-12 18:41:24.928:INFO::jetty-6.1.26
2011-06-12 18:41:24.959:INFO::Started SocketConnector@0.0.0.0:8080

Browsing to localhost:8080 shows a page with “Hello world” on it. Good. It works.

We now change the handler function to instead respond with, say, “Hello everyone” and save it. When we reload the page, the new message appears. Excellent. It reloads automatically.

OK. We’re happy with our web application and want to deploy it into the cloud. What do we do?

Deploying on Heroku

Heroku is, as I mentioned at the beginning, mainly a Ruby application platform. The command line tool for working with Heroku is a Ruby Gem. We first need to install Ruby and RubyGems. Assuming that has been done, we need to get the heroku gem:

$ sudo gem install heroku

Now we can create an app on the Cedar stack:

$ heroku create --stack cedar
Enter your Heroku credentials.
Email: john.doe@example.com
Password:
Found existing public key: /Users/john/.ssh/id_rsa.pub
Would you like to associate it with your Heroku account? [Yn]
Uploading ssh public key /Users/john/.ssh/id_rsa.pub
Creating furious-sword-794... done, stack is cedar
http://furious-sword-794.herokuapp.com/ | git@heroku.com:furious-sword-794.git
Git remote heroku added

We can list the remote repositories to see what the last sentence above meant:

$ git remote -v
heroku	git@heroku.com:furious-sword-794.git (fetch)
heroku	git@heroku.com:furious-sword-794.git (push)

And finally, in order to deploy our first verison of the web app, we push everything to heroku:

$ git push heroku master
The authenticity of host 'heroku.com (50.19.85.156)' can't be established.
RSA key fingerprint is 8b:48:5e:67:0e:c9:16:47:32:f2:87:0c:1f:c8:60:ad.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'heroku.com,50.19.85.156' (RSA) to the list of known hosts.
Counting objects: 13, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (13/13), 1.27 KiB, done.
Total 13 (delta 0), reused 0 (delta 0)

-----> Heroku receiving push
-----> Clojure app detected
-----> Installing Leiningen
       Downloading: leiningen-1.5.2-standalone.jar
       Writing: lein script
-----> Installing dependencies with Leiningen
       Running: lein deps :skip-dev
       Downloading: org/clojure/clojure/1.2.1/clojure-1.2.1.pom from central
       Downloading: ring/ring-core/0.3.8/ring-core-0.3.8.pom from clojars
       ...
       Copying 10 files to /tmp/build_19juox87ngduz/lib
-----> Discovering process types
       Procfile declares types -> web
-----> Compiled slug size is 11.0MB
-----> Launching... done, v4
       http://furious-sword-794.herokuapp.com deployed to Heroku

To git@heroku.com:furious-sword-794.git
 * [new branch]      master -> master

We browse to http://furious-sword-794.herokuapp.com, and we see the text “Hello world” (if you remembered to change back from “Hello everyone”). Now we make a small change:

-  (response "Hello World"))
+  (response "Hello World, I tell you!"))

All we need to do to deploy the change is to commit it, and then push to heroku:

$ git ci -a
$ git push heroku master

When we reload the page, the new text is there.

Roll back to previous release

We can list the releases that have been pushed:

$ heroku releases
Rel   Change                          By                    When
----  ----------------------          ----------            ----------
v5    Deploy 34516b0                  john.doe@examp..  3 minutes ago
v4    Deploy e471ca1                  john.doe@examp..  5 minutes ago

Let’s say there was a problem with the release that was just deployed, and we want to roll back. With Heroku, it’s trivial to roll back to the previous release:

$ heroku rollback
Rolled back to v4

When we’re done with this web app completely, we can destroy it like this:

$ heroku apps:destroy

It’s even possible to roll back to earlier releases, but I won’t show that here. This concludes the Clojure-on-Heroku guide. Thank you for your time.

References

  1. Heroku: http://www.heroku.com/
  2. Clojure: http://clojure.org/
  3. Ring library: https://github.com/mmcgrana/ring
  4. Leiningen: https://github.com/technomancy/leiningen
  5. Compojure: https://github.com/weavejester/compojure/wiki
  6. FlightCaster uses Clojure and Heroku: http://www.infoq.com/articles/flightcaster-clojure-rails
  7. I got the idea for this blog from this Gist: https://gist.github.com/1001206

This Post Has 5 Comments

  1. Thanks! This article got me to fire up my Heroku account, that had been sitting idle since my last heroku app using ruby.

  2. I’ve been playing with a Clojure/Ring webapp with a view to deploying on AWS, but heroku looks like a very tempting alternative.

Leave a Reply

Close Menu