Detail-oriented Programming
What is a full-stack web framework, and what is it good for?
Back in the day (which day being 1995 or thereabouts) there was essentially one web framework. It was a Perl module, called CGI.pm, which decoded the CGI request and provided functions to construct HTML and a CGI response. (CGI.pm is still around, but the past tense adds to the mood of misty reminiscence.) It didn’t have a template system (but Perl “here documents” let you embed HTML directly in your script), or session management (you rolled your own—it’s not hard), or a persistence layer (you used the filesystem, or DBI.pm to talk to SQL databases), but it was simple, easy to use, and easy to understand.
Now, after more than a decade of progress, we have this gigantic Java stack trace of a system running various frameworks and libraries. Somehow I don’t think this monster of delegation is the “full stack” that people have in mind. This setup does not seem easy to use and understand. How did we get here? As usual, with the best of intentions. We’re programmers, so we invented abstractions.
A lot of frameworks seem to be motivated by a handful of interrelated fallacies:
- Abstraction is always a good thing.
- All applications have common functions that can be usefully abstracted out.
- There are details you don’t have to think about.
It is natural and valuable to seek abstractions when programming. A well-chosen abstraction can eliminate pages of similar code, and make the program easier to understand. But abstraction has a cost: it is a layer between you and what is actually going on, so it can also impede understanding. Carving up a program into useful abstractions is an art.
Can we abstract out the common functions of applications? Sure. From 30,000 feet you can’t tell the difference between a Porsche and a Pinto, so go ahead and abstract out a Car. Is this a useful abstraction? A Porsche goes a lot faster than a Pinto. A Pinto blows up when you crash it. Whether Car is a useful abstraction depends on whether these qualities matter to your application. In the same way, whether the abstractions the framework provides are useful depends on your application. But you’ll pay the cost either way.
The worst possible use of abstraction is as a fig leaf. These frameworks, with their menagerie of strategies, managers, and delegators, seem to ascribe no cost to complexity. “You just write your application,” they seem to say, “and we’ll take care of the details”. You can hide arbitrary amounts of complexity, confusion, and bad design behind a nice-looking interface. The problem is that it won’t stay there. When your application isn’t fast enough, or you have a bug that doesn’t seem to be in the code you wrote, either you play Black Box, or you dive into the framework code. At that point, the layers of abstraction start to really hurt. Ultimately, there are no details in a program that you don’t have to think about—all programming is detail-oriented programming.
At Skydeck we are in recovery from frameworks. In the past we’ve used Java, Tomcat, JSP, and a home-grown object-relational mapping tool on top of JDBC and MySQL. We never went all the way down the path of MVC, IoC, JDO, JTA, EJB, and all the other framework-related acronyms, but we had our share of deep stack traces.
This time we’re trying to keep it simple. For web programming we’re using the excellent Ocamlnet. Ocamlnet does what CGI.pm did, and does it simply and well. It also does a ton of other useful things (HTTP and SMTP clients, MIME message parsing and construction, character set translations), but those features are structured as a set of libraries that you can call when you need them rather than as a framework that you have to take all or nothing. When we need to dive into the source, it’s written in a reasonable language.
For storing data persistently, we have been using MySQL and OCaml-MySQL, with no vendor abstraction layer or object-relational mapping. Lately we’ve been feeling that this isn’t the right default, but we’ll save that story for a later post.
I’m not arguing that you should reinvent the wheel, or that real programmers should code on bare metal, but rather that every abstraction has a cost; that web frameworks are often collections of expensive abstractions; and that you should make sure you’re getting good value for your expense.