Building School Seating Charts in Clojure and ClojureScript: Part 1

School Seating Charts

I recent­ly launched a lit­tle side-project web app, School Seating Charts, which makes it eas­i­er (and faster!) for teach­ers to build seat­ing charts for their class­rooms. The site is built entire­ly in Clojure and ClojureScript, which have been a plea­sure to work with.

While writ­ing this post, I real­ized that, for bet­ter or worse, I have a lot to say about Clojure and ClojureScript devel­op­ment. So, to make my life eas­i­er I’ll be split­ting my thoughts into sev­er­al posts. In this first post, I will give a gen­er­al overview of my devel­op­ment expe­ri­ence, and in future posts I will dive more deeply into the details.

Tooling

To build the app, man­age depen­den­cies, and gen­er­al­ly keep myself from set­ting my hair on fire, I use Leiningen. There’s not much that needs to be said here — if you’re using Clojure with­out Leiningen, you are doing it wrong!

I devel­op Clojure with Vim, which puts me in the (slight) minor­i­ty ver­sus Emacs users. Overall, while the LISP-edit­ing tools in Emacs are prob­a­bly more refined, edit­ing Clojure in Vim is not bad at all. Between VimClojure and Paredit.vim, I’m not left want­i­ng. If you go down the VimClojure route, be sure to use the lein-tar­si­er Leiningen plu­g­in, which makes it much eas­i­er to get Vim talk­ing to an instance of your app.

Finally, of course, I use the lein-cljs­build Leiningen plu­g­in for com­pil­ing and test­ing my ClojureScript code (as the author of the plu­g­in, it would be a bit weird if I did­n’t). School Seating Charts is the rea­son that lein-cljs­build exists in the first place — it’s a clas­sic exam­ple of an open source project scratch­ing its author’s itch.

Production Environment

The app runs on Google App Engine (GAE). Now, I work for Google, so I’m prob­a­bly biased (although I began work­ing for Google well after I chose to tar­get GAE), but I have been real­ly hap­py with it so far as a Clojure deploy­ment target.

GAE sup­ports Clojure by virtue of the fact that it sup­ports Java. There’s a lot of interop required to work with the GAE APIs, but luck­i­ly the appengine-mag­ic library has already tak­en care of almost all of this by wrap­ping the Java APIs in an idiomat­ic Clojure API. With this in place, it real­ly feels like there’s native sup­port for Clojure.

Like many oth­er cloud providers, GAE takes care of a ton of the fun­da­men­tals for a web app: the data­base, mem­cache, serv­er infra­struc­ture, load bal­anc­ing, log­ging, met­rics, and a lot more. This con­ve­nience comes with many restric­tions, though, such as lim­it­ed access to local files and out­go­ing net­work con­nec­tions. However, my app’s require­ments hap­pened to mesh nice­ly with the fea­tures and lim­i­ta­tions of GAE, which I think is a large part of the rea­son I’ve been so hap­py with it.

Deployment to GAE is sim­ple. The lein appengine-pre­pare com­mand gets things ready and then the GAE SDK takes it from there and uploads and starts the new ver­sion of the app.

ClojureScript: The Good

I’m ambiva­lent about the JavaScript lan­guage. I don’t hate it, but nei­ther do I like it. So, after I first read about ClojureScript, I jumped at the oppor­tu­ni­ty to write my app’s client-side code in a LISP.

Overall, ClojureScript has been an absolute blast. It has numer­ous advan­tages, such as a sol­id name­space sys­tem, com­pile-time macros, and much of the oth­er good­ness that one would expect from Clojure. The sin­gle biggest win, though, is being able to freely share code between the client and the serv­er. Of course, this can be done in JavaScript with node.js, but JavaScript is real­ly just not that good of a serv­er-side lan­guage. Performance aside, I’d pre­fer Clojure sim­ply due to it’s access to the mas­sive ecosys­tem 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 selec­tors. The HTML gen­er­at­ed by the serv­er and the DOM lookups made on the client always agree because the IDs and selec­tors are defined in one spot. This helps to pre­vent a whole class of errors from those things being mismatched.

The app’s con­fig API is shared between the client and serv­er. So, things like “is debug mode on” or “what’s the price of the app” come from a cen­tral place and are han­dled by the same code, so the client and serv­er always agree on their val­ues. Other shared code includes var­i­ous geom­e­try util­i­ties that are required in both places.

The best exam­ple of why I love using the same lan­guage on the client and serv­er prob­a­bly has to do with the code I wrote to shuf­fle stu­dents around the class­room. The teacher lays out desks and inputs a stu­dent ros­ter, and then can press a but­ton to ran­dom­ize the stu­dent place­ment, all the while respect­ing oth­er cri­te­ria (e.g. keep talk­ers away from one anoth­er). I orig­i­nal­ly exe­cut­ed this algo­rithm on the client, which worked fine in mod­ern browsers, but did­n’t per­form very well in IE8. After con­sid­er­ing my options (rewrite the code in JavaScript, sim­pli­fy the algo­rithm), I decid­ed to move the cal­cu­la­tions to the serv­er and have the client retrieve them via XHR. This change took me around 10 min­utes to imple­ment. To me, this is mind-blow­ing. The algo­rithm in ques­tion is pret­ty tricky, with lots of edge-cas­es, and even a straight port between lan­guages would have tak­en hours and intro­duced bugs.

ClojureScript: The Bad

So, what are ClojureScript’s rough edges? Well, there are a few things. For one, with so few peo­ple using the lan­guage, you are more like­ly to run into edge-cas­es that haven’t had the bugs beat­en out of them. For instance, I used ClojureScript’s pr-str func­tion to seri­al­ize data struc­tures to send to the serv­er. Apparently, not many oth­er peo­ple had tried using pr-str with a large data struc­ture on IE8. Performance was unus­ably bad, and I end­ed up hav­ing to patch the com­pil­er to get accept­able performance.

Debugging is anoth­er rough edge. At the time of writ­ing, ClojureScript does not have source map sup­port, which means that when your code throws an error at run­time, you’re stuck look­ing at a JavaScript stack trace with lit­tle to no ClojureScript-spe­cif­ic infor­ma­tion. Personally, I found the gen­er­at­ed JavaScript pret­ty easy to read, as it retains most of the sym­bols from the code it was com­piled from. Regardless, this clear­ly needs to improve. Thankfully, peo­ple are work­ing on it.

ClojureScript, like Clojure, is a host­ed lan­guage. This means that interop with the JavaScript plat­form is a first-class fea­ture. Overall, interop with JavaScript code is impres­sive­ly easy. School Seating Charts makes exten­sive use of jQuery and sev­er­al jQuery plugins.

The com­pil­er is imple­ment­ed on top of the Google Closure Compiler (yes, the ter­mi­nol­o­gy is extreme­ly con­fus­ing), which means that ClojureScript can take advan­tage of its excel­lent opti­miz­er for things like dead code elim­i­na­tion and com­pres­sion. This is absolute­ly nec­es­sary for pro­duc­tion deploy­ment, as for School Seating Charts, the JavaScript out­put is 1.8MB before opti­miza­tion (it’s 188K after opti­miza­tion, and 46K after gzip).

However, the advanced opti­miza­tions come at a cost: if your ClojureScript code calls into any exter­nal JavaScript libraries, you must pro­vide an externs file to tell the com­pil­er which sym­bols need to be passed through uncom­pressed (Luke VanderHart wrote an excel­lent post about this). The doc­u­men­ta­tion on how to cre­ate these externs files is very poor, and for me, at least, required a lot of frus­trat­ing tri­al and error. While this lack of doc­u­men­ta­tion is ulti­mate­ly a Google Closure Compiler prob­lem, it very much affects ClojureScript devel­op­ment as well.

With all of that said, please don’t take my crit­i­cisms of ClojureScript too seri­ous­ly. In real­i­ty, it’s inspir­ing that the lan­guage is scarce­ly 14 months old and yet is total­ly usable for pro­duc­tion sys­tems. The com­mu­ni­ty around the lan­guage is aware of all rough edges that I high­light­ed, and there’s work being done to address them all. 

Appendix: Libraries

This is just a sur­vey of all of the app’s direct depen­den­cies, tak­en from its project.clj con­fig file. Some of these libraries are pret­ty spe­cif­ic to the way the app is built (stripe-java), and oth­ers are like­ly to be found in every Clojure web app out there (com­po­jure).

  • appengine-mag­ic
    Makes it much eas­i­er to write a Google App Engine app in Clojure.
  • clj-stack­trace
    Handy for print­ing out nice­ly for­mat­ted (and col­ored) stacktraces.
  • clj-time
    An idiomat­ic Clojure wrap­per around Java’s Joda Time library.
  • com.cemerick/friend
    Makes authen­ti­ca­tion and autho­riza­tion straight­for­ward for Compojure apps.
  • com.stripe/stripe-java
    With this, the serv­er-side billing code took maybe an hour to write and test.
  • com­po­jure
    A sim­ple and beau­ti­ful HTTP rout­ing lay­er on top of Ring.
  • crate
    Provides the same HTML DSL as Hiccup, but for ClojureScript.
  • hic­cup
    Provides a sim­ple DSL for build­ing well-formed HTML.
  • jayq
    A lit­tle ClojureScript wrap­per around com­mon jQuery features.
  • mid­je
    A test frame­work for Clojure with very pow­er­ful mock­ing capabilities. 
  • prism
    A tiny library to help share code between Clojure and ClojureScript.
  • sling­shot

A nice lit­tle improve­ment on the built-in excep­tion fea­tures in Clojure. 

Part 2

If this post was inter­est­ing, you might want to tune in for the next post in the series. I haven’t writ­ten it yet, but I promise it’s com­ing soon!

Comments are disabled for this post