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?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.
more: grape_flavored