November 22, 2012

My experiences deploying a small Clojure+MongoDB app on Heroku and MongoHQ

Recently, I wrote an app in Clojure (using the Noir framework), with MongoDB (via congomongo) for a data store. The app in question, http://www.ladieschoicevictoria.com/, is a dating website providing privacy for women and efficiency for men by requiring women to send a message before a man can even see their profile.

It's been running for nearly two months with few technical issues, and overall I'm really pleased.

Yes, I did it for the buzzwords

I have been looking for an excuse to use both of these techs, so when Paul came to me with his idea for the site I jumped on it. I could have as easily gotten away with some sort SQL (I hear Korma is great), of course, and any other language I've built an app in before (PHP, Python, Ruby, and counting). But something I've come to learn from the experience is that both Clojure and Mongo are extremely well-suited to banging out an application in record time -- the bulk of the site was written over the course of two long days. And this from someone who had never used Clojure in production before.

Another consideration is that between Heroku's one-free-dyno and MongoHQ's 512MB free, we can maintain the site at no cost until we're well into the revenue zone. Presently, with around 1000 users, we're using somewhat shy of 18MB of database space, and Heroku's single dyno has handled our traffic admirably, with one exception that I will detail below.

Schema-less schemas

The phrases "schema-less" and "database" put together are a near oxymoron; for data to be any use at all, it must be predictably structured. What people mean when they say that Mongo is "schemaless" is that anyone using mongo is enforcing the schema in their application code, rather than relying on the database to do it. You should know what fields and what data types to expect when you save and load data through your schema code.

This is something that can be packed into a library that incurs no more baggage than sql-based schema validation. I spun off the schema-management parts of the application as an open-source component called schemongo, which provides basic validation to help ensure that you're always storing what you think you're storing.

Multiple-personaity Templates

For templating, the site primarily uses Mustache. I've written about it before, but I'll recap why this decision was made: Anyone with a passing familiarity with HTML would be able to edit the templates, since the syntax for template variables is so, so simple. Mustache is the most thorough way of separating presentation and content that I'd yet found. However, I've since been alerted to Enlive (and Middleman), and will probably try that out for another site in the future.

Forms, though, tend to use the awesome hiccup form helpers. After all, these are the parts of the frontend that the developer needs to do anyhow. I believe that doing the whole page in hiccup is at very least something that can't or shouldn't be sustained forever, if you plan on ever involving someone that doesn't know LISP in your design workflow, but automating form construction is a different matter entirely.

As an illustration, the one form that doesn't use hiccup is the one that has a longstanding bug with field validation that I just can't get around to fixing. A takeaway for anyone reading this is to always use a form helper for your form HTML (WTForms is a nice Python library, for example); Mustache is nice enough, but it's not for forms. Rendering pages and rendering forms are, simply, different technical problems. I think I've learned this now.

Traffic Spikes, oh my

You know you have an idea that resonates with people when the local paper picks it up. The same day the article was published, we were invited in to the local CBC radio for an interview (side note: I also got to meet Tom Hawthorne, who wrote about that time I shaved half my head for my driver's license in the Globe and Mail. He was on after us. It was a bit surreal).

I spent the bus ride to the interview and the time in the waiting room monitoring the Heroku logs, because it turns out that getting picked up by the media will increase your traffic quite a bit. Our one dyno began failing under the weight, and I had to spin up a few more. This was easy enough to do from my phone, which is saying something for Heroku's ease-of-use.

It wasn't without problems though -- that night I switched from noir's default of in-memory sessions to storing them in mongo. Lesson learned: Heroku does not share memory between dynos.

TL;DR: Would do again.

I've deployed apps built in PHP, Django, Flask and Sinatra, and although all of those have great qualities, I think I've enjoyed the Clojure experiment most of all.