Saturday, September 8

grape flavored

There isn't really much Kool-Aid to drink in this instance, but I thought
I might talk about how much mileage I have recently gotten out of using
CGI::Application, a web application framework.

There are two directions I could go with this. One is for people who might be
contemplating a Perl5 web framework and looking for someone to tip the balance
one way or another, or maybe just give them a sense of what it would be easiest
to do. That essay and some associated points follow, and all but three of my
regular readers probably want to skip it. The other direction is some general
rambling about abstraction in the abstract, which I might get to in a day or
two.

+

Anyway, I had to start a little web application from scratch the other day, so
I started looking for a framework, because I needed a lot of the sort of things
you suspect someone has already written.

My criteria were as follows:

  • My choices are basically PHP or something in Perl5, and PHP
    blows goat dick.
    (I meant to assert that PHP is fantastically successful, and has everything you
    need, as long as you don't need to feel good about yourself in the morning, but
    why obscure the fundamental point?)
  • I need it to be relatively lightweight, without too many hairy dependencies.
    In particular, I would mostly like to stick with pure Perl and avoid modules
    which require compiling C on my target machine.
  • I would like for the basic abstraction to be simple and not terribly
    opinionated.
  • I want to use a nice template language for pages.

I looked at Catalyst, Jifty, and CGI::Application. The first two were
interesting, but fell down on the dependency question. For purely pragmatic
reasons, I doubted it would be worth the effort installing them on my target
machine, and I suspect I was right. As it stands, I spent nearly a day on
DBD::SQLite, the single XS module I wound up using. I'd prefer not to talk
further about that experience, beyond noting in passing that just because it
feels wrong to wrap

perl Makefile.PL
make

in a CGI script, FTP it to the right location, and watch something build in
your browser doesn't mean that you shouldn't give it a shot.

CGI::Application caught my attention because its core abstraction is basically
a dispatch table in the context of use CGI. Here is a concrete example:

# in FieldTrip.pm

package FieldTrip;
use base 'CGI::Application';

sub setup {
  my $self = shift;

  $self->start_mode('viewtrip');

  $self->run_modes(
    'viewtrip' => 'viewtrip',
  );
}

sub viewtrip {
  my $self = shift;
  return "Here is a trip.\n";
}

1;

# and then in a wrapper script called ft.cgi...

use FieldTrip;
my $ft = FieldTrip->new;
$ft->run;

ft.cgi can now be called like ft.cgi?rm=viewtrip, and you'll get the
appropriate result. Actually, you'll get it even without the runmode parameter,
since viewtrip is also the start mode. This is pretty darned simple. It
certainly didn't require me to wrap my brain around much before I could
evaluate its merits in real code.

CGI::Application also comes with a plugin architecture, and the plugins have
so-far tended to be nicely discrete pieces of functionality which work as
advertised. The above becomes something like this, with a little help:

package FieldTrip;
use base 'CGI::Application';
use CGI::Application::Plugin::AutoRunmode;

sub viewtrip : StartRunmode {
  my $self = shift;
  # etc.
}

1;

Here's my list of plugins:

  • CGI::Application::Plugin::DBH
  • CGI::Application::Plugin::Redirect
  • CGI::Application::Plugin::Session
  • CGI::Application::Plugin::AutoRunmode
  • CGI::Application::Plugin::Authentication
  • CGI::Application::Plugin::TT

Some other modules attracted me on the basis of simple/powerful:

  • Template Toolkit has nice syntax, any control structure you are likely
    to want, broad configurability, and good integration with CGI::A. I used
    CGI::Application::Plugin::TT, but there's another option called AnyTemplate.
  • Nate Wiger's SQL::Abstract.
  • Class::Accessor generates getter/setter methods for you, which saves a ton of
    boilerplate code.
  • DBD::SQLite provides a DBI interface to SQLite and actually bundles a
    distribution of same. I dind't have access to MySQL, and probably don't need
    it for the 30 or 40 uses a day this thing is likely to see. This hasn't been
    without its headaches, but overall I think it was the right idea.

In the end, I wound up with a project that looks a little like this:

  • FieldTrip.pm - The app proper.
  • index.cgi - Wrapper script.
  • fieldtrip.db - Data store.
  • lib/ - Dependencies.
  • pages/ - Templates.
  • resources/ - CSS, image files, JavaScript.
  • Thing.pm - A base class which deals with pulling things out of the db
    and shoving them back in. Subclasses provide a list of fields
    and a table to draw on, along with methods for things like
    "render this notes field as HTML" or "calculate this person's
    age". It is like the world's most ghetto quasi-ORM, and it
    was probably a mistake not to get Class::DBIx working instead.

Within FieldTrip.pm, my runmodes all look like this:

sub viewtrip : Runmode {
  return $_[0]->thing_out( 
    "Thing::Trip",
    'View trip details'
  );
}

Which involves some contortions elsewhere that I should probably simplify
(maybe by just generating view, edit, and save modes for each kind of Thing
with Class::Accessor), but is pretty close to declarative. Adding a runmode
can be as simple as four or five lines of code in FieldTrip.pm and an
appropriately named template in the pages directory.

Mid-way through this project, I realized that I could spend a little
extra time and sell a small application built on exactly the same framework to
another client. In order to do this, I had to put a single table in a SQLite
database, create a 20-line subclass of Thing.pm, and rewrite a couple of
templates. This was remarkably painless.