Custom Protocols Considered Harmful: Thin & Rack are the Platform of the Future

Jun 01, 2008

While I was figuring out how to embed a webserver in to my new open source project, I had an epiphany: REST is the real deal, and thin, rack, and invisible are the platform of the future. It turns out that most daemon processes require a lot of code just to implement a protocol, daemonize, and manage things like PID files. From what I can tell, most of the code in many of these utilities is there to do things that aren't their primary responsibility.

Take Starling, Twitter's queueing agent, for example. A quick sloccount of starling's lib directory shows that there are 737 lines of code in that gem. Further investigation shows that the service (that is, everything but the client) is comprised of 5 files.

  1. server.rb is responsible for things like starting the server, processing requests at the socket level, and managing Starling's thread pool. server.rb contains 146 LoC.
  2. runner.rb is responsible for parsing command line options, daemonizing, switching to a specified user and/or group, forking, etc. runner.rb contains 199 LoC.
  3. handler.rb implements the memcache protocol. It contains 143 LoC.
  4. persistent_queue.rb actually implements — well — the persistent queue. It accepts new queue items, and stores them on disk for later retrieval. persistent_queue.rb contains 103 LoC.
  5. queue_collection.rb is a proxy to a collection of PersistentQueue instances. I took that last sentence from their documentation, because I'm actually not 100% clear on what this file does. At any rate, queue_collection.rb contains 79 LoC.

As we can see, most (66%) of Starling is completely unrelated to queueing. Sure, implementing the memcache protocol was a good idea, since it's well supported, but at what cost? The author of Starling was forced to write quite a bit of new code.

All new code is immature. There are always bugs to fix, and issues that arise. But, this kind of new code has got a much bigger problem: most of it is unrelated to Starling's domain, and it isn't generic enough to be useful for anybody but them. So, Starling's daemonizing code, and memcache implementation are only being looked after by Starling's developers.

While Starling is fairly popular, it's a pretty specific tool. So, the base of contributors, bug finders, and bug fixers is far smaller than for, say, a web server.

Thin & Rack as a Platform

A web server needs to do all of the same ugly stuff as Starling. It needs to know how to daemonize, and speak a protocol (REST!). The difference? That stuff is a web server's primary responsibility.

Thin gives you event-driven listening, daemonizing, and stability — all for the price of: '', 5432 do  
  map('/') { run }

Thin's mailing list has over 250 subscribers, and nearly 100 people watch it on github. Plenty of people are running their websites on thin (and, those are just the ones we know about). And, while most of the work is still being done by MA, there have been plenty of contributions from other people. And, given that a web server is a much more widely needed application, thin has a much larger potential user base than Starling.

Piggybacking on thin's technology is a good idea, and it's incredibly easy to do. Thin has built-in support for rack, a generic layer for connecting a web server to an application. It does all the stuff that every web framework used to have to do. Rack makes writing a framework insanely easy.

Invisible: A Web Framework in 40 sloc

The last time I saw MA speak, he coded a version of invisible live, during an hour long talk. Earlier today, I forked it, and made some changes to tailor it toward writing web services that are consumable by ActiveResource. It is an MVC framework, but my fork has no support for templating, since most responses will be of the to_xml variety.

The controllers look a lot like rails controllers. Like in merb, the return value of an action becomes the response body. However, things like changing the status code are decidedly more bare bones than in most frameworks. Also, there is no user-specified routing at all: all routes map to /controller/:id, with the action being dependent on the REST method.

These two hashes determine which action gets called (i.e. exactly how ActiveResource expects them):

MEMBER_ACTIONS     = { 'PUT' => :update, 'DELETE' => :destroy, 'GET' => :show }
COLLECTION_ACTIONS = { 'POST' => :create, 'GET' => :index }

If you follow invisible's conventions (and, it's hard not to), your service should be consumable by active_resource, out of the box. That means you get a free client!

Since we all know that a framework is nothing without a sample app...

Scrawny: A Lightweight, RESTful Persistent Queue on Top of thin, rack and invisible in < 60 sloc

Before we get started, I want to make something clear from the get go: THIS IS A PROOF OF CONCEPT — I KNOW IT ISN'T FINISHED — DON'T USE IT!

It's unfinished, but scrawny displays the amazing power of thin, rack, invisible, and active_resource. With an incredibly small amount of code, I was able to implement a stable, lightweight, persistent message queue, and its client. The code for doing all of the ugly stuff I talked about earlier in this article is a mere ~10 lines. I was able to focus on actually creating the queue. The other stuff was an aside, as it should be.