by Ulrik Sandberg - Cloud, Dynamic languages, Java
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.
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:
|
1 2 3 4 5 |
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.
|
1 |
(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:
|
1 2 3 |
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:
|
1 2 |
user> (:name animal) "Leo" |
This form is more common in idiomatic Clojure.
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:
|
1 2 |
GET /welcome HTTP/1.1 HOST: www.sayhello.com |
Ring transforms this into a Clojure map that looks like this:
|
1 2 3 4 |
{: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:
|
1 2 3 4 5 |
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:
|
1 2 3 4 5 |
{: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:
|
1 2 3 4 5 6 |
(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:
|
1 2 3 4 |
" ] (use 'ring.util.response) (defn app [req] (response "Hello world")) |
We’ll use the response function in our example later.
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:
|
1 2 3 4 5 6 |
$ 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 |
We create a new project and go there:
|
1 2 |
$ lein new cljheroku $ cd cljheroku |
The newly created directory contains the following files:
|
1 2 3 4 5 6 7 8 9 10 |
cljheroku/ ├── README ├── project.clj ├── src │ └── cljheroku │ └── core.clj └── test └── cljheroku └── test └── core.clj |
First we need to create a Procfile for Heroku:
|
1 |
$ 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:
|
1 2 3 4 5 6 7 8 9 10 |
(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:
|
1 2 3 |
(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.
|
1 2 |
(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.
|
1 2 3 |
(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:
|
1 2 3 4 5 6 |
(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:
|
1 2 3 4 5 6 |
$ 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.
Before we test it, we will ask Leiningen to retrieve the dependencies:
|
1 |
$ 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:
|
1 |
$ 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:
|
1 |
$ 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):
|
1 2 3 4 |
$ 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?
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:
|
1 |
$ sudo gem install heroku |
Now we can create an app on the Cedar stack:
|
1 2 3 4 5 6 7 8 9 10 |
$ 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:
|
1 2 3 |
$ 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:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
$ 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:
|
1 2 |
- (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:
|
1 2 |
$ git ci -a $ git push heroku master |
When we reload the page, the new text is there.
We can list the releases that have been pushed:
|
1 2 3 4 5 |
$ 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:
|
1 2 |
$ heroku rollback Rolled back to v4 |
When we’re done with this web app completely, we can destroy it like this:
|
1 |
$ 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.
Greetings! Very helpful advice in this particular post! It’s the little changes which will make the greatest changes. Thanks for sharing!
Thank you, guys. You’ll appreciate the recently announced official support for Clojure on Heroku, as described here: http://www.jayway.com/2011/07/07/clojure-third-language-officially-supported-on-heroku/
I’ve been playing with a Clojure/Ring webapp with a view to deploying on AWS, but heroku looks like a very tempting alternative.
Thanks! This article got me to fire up my Heroku account, that had been sitting idle since my last heroku app using ruby.
Thanks for a very good article!