Building School Seating Charts in Clojure and ClojureScript: Part 1

I recently launched a little side-project web app, School Seating Charts, which makes it easier (and faster!) for teachers to build seating charts for their classrooms. The site is built entirely in Clojure and ClojureScript, which have been a pleasure to work with.

While writing this post, I realized that, for better or worse, I have a lot to say about Clojure and ClojureScript development. So, to make my life easier I’ll be splitting my thoughts into several posts. In this first post, I will give a general overview of my development experience, and in future posts I will dive more deeply into the details.

Tooling

To build the app, manage dependencies, and generally keep myself from setting my hair on fire, I use Leiningen. There’s not much that needs to be said here — if you’re using Clojure without Leiningen, you are doing it wrong!

I develop Clojure with Vim, which puts me in the (slight) minority versus Emacs users. Overall, while the LISP-editing tools in Emacs are probably more refined, editing Clojure in Vim is not bad at all. Between VimClojure and Paredit.vim, I’m not left wanting. If you go down the VimClojure route, be sure to use the lein-tarsier Leiningen plugin, which makes it much easier to get Vim talking to an instance of your app.

Finally, of course, I use the lein-cljsbuild Leiningen plugin for compiling and testing my ClojureScript code (as the author of the plugin, it would be a bit weird if I didn’t). School Seating Charts is the reason that lein-cljsbuild exists in the first place — it’s a classic example of an open source project scratching its author’s itch.

Production Environment

The app runs on Google App Engine (GAE). Now, I work for Google, so I’m probably biased (although I began working for Google well after I chose to target GAE), but I have been really happy with it so far as a Clojure deployment target.

GAE supports Clojure by virtue of the fact that it supports Java. There’s a lot of interop required to work with the GAE APIs, but luckily the appengine-magic library has already taken care of almost all of this by wrapping the Java APIs in an idiomatic Clojure API. With this in place, it really feels like there’s native support for Clojure.

Like many other cloud providers, GAE takes care of a ton of the fundamentals for a web app: the database, memcache, server infrastructure, load balancing, logging, metrics, and a lot more. This convenience comes with many restrictions, though, such as limited access to local files and outgoing network connections. However, my app’s requirements happened to mesh nicely with the features and limitations of GAE, which I think is a large part of the reason I’ve been so happy with it.

Deployment to GAE is simple. The lein appengine-prepare command gets things ready and then the GAESDK takes it from there and uploads and starts the new version of the app.

ClojureScript: The Good

I’m ambivalent about the JavaScript language. I don’t hate it, but neither do I like it. So, after I first read about ClojureScript, I jumped at the opportunity to write my app’s client-side code in a LISP.

Overall, ClojureScript has been an absolute blast. It has numerous advantages, such as a solid namespace system, compile-time macros, and much of the other goodness that one would expect from Clojure. The single biggest win, though, is being able to freely share code between the client and the server. Of course, this can be done in JavaScript with node.js, but JavaScript is really just not that good of a server-side language. Performance aside, I’d prefer Clojure simply due to it’s access to the massive ecosystem of Java libraries.

What kinds of code does School Seating Charts share between Clojure and ClojureScript? Well, my favorite thing is all of the HTML IDs and CSS selectors. The HTML generated by the server and the DOM lookups made on the client always agree because the IDs and selectors are defined in one spot. This helps to prevent a whole class of errors from those things being mismatched.

The app’s config API is shared between the client and server. So, things like “is debug mode on” or “what’s the price of the app” come from a central place and are handled by the same code, so the client and server always agree on their values. Other shared code includes various geometry utilities that are required in both places.

The best example of why I love using the same language on the client and server probably has to do with the code I wrote to shuffle students around the classroom. The teacher lays out desks and inputs a student roster, and then can press a button to randomize the student placement, all the while respecting other criteria (e.g. keep talkers away from one another). I originally executed this algorithm on the client, which worked fine in modern browsers, but didn’t perform very well in IE8. After considering my options (rewrite the code in JavaScript, simplify the algorithm), I decided to move the calculations to the server and have the client retrieve them via XHR. This change took me around 10 minutes to implement. To me, this is mind-blowing. The algorithm in question is pretty tricky, with lots of edge-cases, and even a straight port between languages would have taken hours and introduced bugs.

ClojureScript: The Bad

So, what are ClojureScript’s rough edges? Well, there are a few things. For one, with so few people using the language, you are more likely to run into edge-cases that haven’t had the bugs beaten out of them. For instance, I used ClojureScript’s pr-str function to serialize data structures to send to the server. Apparently, not many other people had tried using pr-str with a large data structure on IE8. Performance was unusably bad, and I ended up having to patch the compiler to get acceptable performance.

Debugging is another rough edge. At the time of writing, ClojureScript does not have source map support, which means that when your code throws an error at runtime, you’re stuck looking at a JavaScript stack trace with little to no ClojureScript-specific information. Personally, I found the generated JavaScript pretty easy to read, as it retains most of the symbols from the code it was compiled from. Regardless, this clearly needs to improve. Thankfully, people are working on it.

ClojureScript, like Clojure, is a hosted language. This means that interop with the JavaScript platform is a first-class feature. Overall, interop with JavaScript code is impressively easy. School Seating Charts makes extensive use of jQuery and several jQuery plugins.

The compiler is implemented on top of the Google Closure Compiler (yes, the terminology is extremely confusing), which means that ClojureScript can take advantage of its excellent optimizer for things like dead code elimination and compression. This is absolutely necessary for production deployment, as for School Seating Charts, the JavaScript output is 1.8MB before optimization (it’s 188K after optimization, and 46K after gzip).

However, the advanced optimizations come at a cost: if your ClojureScript code calls into any external JavaScript libraries, you must provide an externs file to tell the compiler which symbols need to be passed through uncompressed (Luke VanderHart wrote an excellent post about this). The documentation on how to create these externs files is very poor, and for me, at least, required a lot of frustrating trial and error. While this lack of documentation is ultimately a Google Closure Compiler problem, it very much affects ClojureScript development as well.

With all of that said, please don’t take my criticisms of ClojureScript too seriously. In reality, it’s inspiring that the language is scarcely 14 months old and yet is totally usable for production systems. The community around the language is aware of all rough edges that I highlighted, and there’s work being done to address them all.

Appendix: Libraries

This is just a survey of all of the app’s direct dependencies, taken from its project.clj config file. Some of these libraries are pretty specific to the way the app is built (stripe-java), and others are likely to be found in every Clojure web app out there (compojure).

appengine-magic
Makes it much easier to write a Google App Engine app in Clojure.