Building School Seating Charts in Clojure and ClojureScript: Part 1

School Seating Charts

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 GAE SDK 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.

  • clj-stacktrace
    Handy for printing out nicely formatted (and colored) stacktraces.

  • clj-time
    An idiomatic Clojure wrapper around Java’s Joda Time library.

  • com.cemerick/friend
    Makes authentication and authorization straightforward for Compojure apps.

  • com.stripe/stripe-java
    With this, the server-side billing code took maybe an hour to write and test.

  • compojure
    A simple and beautiful HTTP routing layer on top of Ring.

  • crate
    Provides the same HTML DSL as Hiccup, but for ClojureScript.

  • hiccup
    Provides a simple DSL for building well-formed HTML.

  • jayq
    A little ClojureScript wrapper around common jQuery features.

  • midje
    A test framework for Clojure with very powerful mocking capabilities.

  • prism
    A tiny library to help share code between Clojure and ClojureScript.

  • slingshot

A nice little improvement on the built-in exception features in Clojure.

Part 2

If this post was interesting, you might want to tune in for the next post in the series. I haven’t written it yet, but I promise it’s coming soon!

15 Comments

  • William Roe wrote:

    The link to lein-tarsier actually links to lein-cljsbuild.

  • William Roe wrote:

    You made lein-cljsbuild? Awesome! I used it while building my entry to Ludum Dare last weekend:
    http://www.ludumdare.com/compo/ludum-dare-24/?action=preview&uid=14189

    So thanks for that!

  • Oops, thanks for pointing out the incorrect link. As for your Ludum Dare entry, I must say that the premise of the game is hilarious. It’s great to see people doing cool stuff like that in ClojureScript!

  • Awesome post. This should be helpful. Looking forward to your next post.

  • It’d just like to say that I noticed your good typography work on this blog. Reducing the contrast a bit might make it a bit easier on the eyes though. Would suggest off-black for the background. A dark grey will look “black” to most people.

    The content of the post was interesting, and I learned about a few new clojurescript libraries. Looking forward to your next post in the series!

  • Hi!

    Thanks for your article and x100 times more for lein-cljsbuild!

    I’d have a silly question to ask. I’m also using JQuery with CLJS as I’m a bit familiar with it. However aren’t all the basic features of JQuery already implemented in the Google Closure library? In short: isn’t it quite redundant to have closure and jquery in cljs? Many thanks for your thoughts!

  • @Ben: It is true that jQuery and the Google Closure Library (GCL) overlap in terms of many features. However, there are many differences between the two libraries, and you can make an argument for using either of them. In my case, the strongest argument was that jQuery had several extremely useful plugins. For instance, the jQuery UI draggable support is (in my opinion) much easier to use than the equivalent feature in GCL.

    Since ClojureScript depends on GCL, it is less efficient to require jQuery (in terms of bandwidth). However, I’ve found that the :advanced optimizer’s dead code elimination helps a lot, and since I’m not using much from GCL, a lot of it can be eliminated in the final JS. I’m satisfied with the page loading speed with both GCL and jQuery, but I suppose if you wanted to squeeze out every last drop of performance, it would be best to just use GCL. For me, it was worthwhile to trade some page loading speed for a more rapid development cycle.

  • @Alex: I’ve received many complaints about the white-on-black format, but yours was by far the most courteous and helpful. Maaaaybe I’ll noodle around with something a bit less contrasting.

    For the record, though, the white-on-black format doesn’t bother me until I navigate to a black-on-white page and it seems super bright. So, really, I think the rest of the web should change. :)

  • William Roe wrote:

    @Ben: Further to what Evan said, GCL is missing most of the dom selection goodness that is core to jQuery. In GCL, this is an extra you have to get included somehow (it’s not in the clojurescript library afaik). I found it much much simpler to use the jayq library which allows ClojureScript code to use jQuery code: https://github.com/ibdknox/jayq

  • Murtaza Husain wrote:

    Evan, thanks for both the post and lein-cljsbuild. One request, is it possible to release the code under an appropriate proprietary license ? I would love to see the code and learn from it. I have been trying to learn cljs, but there is a dearth of prodcution quality code.

  • @Evan: many thanks for your comment, it’s very clear. Indeed in terms of bandwidth I do agree with you and doubt JQuery would add a lot since I guess any web browser already has a copy of a CDNized jquery in its cache. Many thanks again and please continue to enlighten us! Ben.

  • @Murtaza: While I’d love to share the source code with the Clojure community, I don’t think it’s in my business’ best interest to release the source code. If I did release it, it would be trivial for someone to put up their own copy on GAE and compete with me. Of course, with a proper license I could sue them, etc, but I don’t want to deal with that.

    What’s more likely to happen is that I will release specific components as open source projects (like lein-cljsbuild, which started off as part of School Seating Charts). Of course, this doesn’t provide the overall big picture of how the app works, but I think it’s the best I can do for now.

  • Hi,
    for your next project or to keep everything more clojure possible I want to let you know that exist clj-stripe ( https://github.com/abengoa/clj-stripe ) before to consider the use please give a look at the open issue.

    Greets

  • @siscia: That looks cool. It’s a shame that it’s still not in the Stripe docs. They even have a section for unofficial APIs, so I don’t see why they wouldn’t want to list it.

  • @evan: Yes it is. BTW abengoa (aka “the guy behind clj-stripe) just fixed the API so we can easily use it now.
    Cheers

Leave a Reply

Your email is never shared.Required fields are marked *