[Maypole] If you hated Maypole, you'll also dislike...
Simon Cozens
simon@simon-cozens.org
Tue, 20 Jul 2004 23:24:17 +0100
I don't know quite how to describe my latest web application framework, or how
to explain it. If I called it "an abstraction layer above Maypole", you might
think that my code was soon going to disappear up its own backside. Maybe it
is.
While Maypole was designed to be extended "vertically", in the sense of making
it very easy to add new actions to a class - one template, one method, and
you're done - it isn't so easy to extend "horizontally". You can't easily add
new ideas and concepts (as represented by tables and classes) and have them
fit in nicely with the rest of the application.
This is a problem I looked at and solved when designing my Email::Store set of
mail archiving modules - I wanted it to be possible for third-parties to add
plugin modules to archive information they were interested in, and have their
plugins feature as first-class members of the Email::Store system.
I identified three things which could make this happen: embedded SQL, so that
modules could define their own tables, provided by Class::DBI::DATA::Schema;
a plugin loader for modules, provided by Module::Pluggable; and a pipelined
system of trigger points, so that plugins could influence each other's
behaviour, provided by what became Module::Pluggable::Ordered.
Further, I needed Class::DBI::DATA::Schema's translation option so that the
same schema code could work no matter what the database.
For my next Maypole project, I needed the same style of pluggability, and
realised that this three-way pluggability approach works equally well for
Maypole classes. By tying the three techniques into Maypole, I created a
Maypole-based framework that "stretches" in all directions - vertically with
the simple addition of new actions, and horizontally with the simple addition
of completely new classes. The "stretchy" result is called Rubberband, and
it's available from CPAN.
Here's an example of how I'm currently using Rubberband. I'm developing a
blogging tool called Feuilleton. It has certain "core" concepts: blogs, posts,
users, and so on. It then has "plugin" concepts: comments, blogrolls.
Here's what Feuilleton.pm currently looks like:
package Feuilleton;
use Rubberband (
dsn => "dbi:mysql:feuilleton",
search_path => [qw( Feuilleton::Core Feuilleton::Plugin )],
translate_sql_from => "mysql"
);
Feuilleton->config->{auth}{user_class} = "Feuilleton::Core::User";
Feuilleton->config->{base_url} = "http://localhost/feuilleton/";
Feuilleton->config->{template_root} =
"/Users/simon/maypole-stuff/feuilleton/templates";
sub fresh_start {
Feuilleton->create_database_tables();
Feuilleton->call_plugins("on_create_database");
}
Unlike Maypole::Model::CDBI, Rubberband doesn't (by default) look at the
database to load its classes, but looks at the plugin modules: the
"search_path" line tells it to load up all of Feuilleton::Core::* and
Feuilleton::Plugin::* and use them as Maypole classes *if* they are attached
to a database table. "translate_sql_from" describes the data language that
the SQL schema is going to be written in. Rubberband provides
"create_database_tables", which runs the SQL statements embedded in every
module, and "call_plugins", which fires off a trigger point. All will become
clear when you see Feuilleton::Core::Blog:
package Feuilleton::Core::Blog;
use strict;
__PACKAGE__->table("blog");
__PACKAGE__->columns(All => qw[id name]);
sub view :Exported { }
sub on_create_database_order { 1 }
sub on_create_database {
Feuilleton::Core::Blog->create({ name => "My new blog" });
}
__DATA__
CREATE TABLE blog (
id integer not null primary key auto_increment,
name varchar(255)
);
When we call "Feuilleton->fresh_start", the "CREATE TABLE blog" statement
is translated from mysql SQL to the local SQL, (which currently happens
to be mysql, but the value of "dsn" will change) and then executed, along
with any other SQL statements in data sections. "Blog" just happens to
be core, but the distinction between core and plugin is deliberately minimal.
Next, the "on_create_database" subroutines are collated from all plugin
modules, and sorted by the "on_create_database_order" of each class, and
then run in turn; this allows all the plugins to take their turns at setting
up the data they need when the application is started.
Rubberband is still very much in the early stages of development, and
just like with Maypole itself, things change as I work out the easiest and
best ways of working with it, but if you're brave, you've got time, and are
prepare to mess about on your own without expecting much in the way of
support or documentation ;-) then grab it from CPAN and give it a look.
I think it's the new way of creating extensible web applications, and I
like it a lot.
--
<deus_x> Anyone who takes words on the screen personally should not be on IRC.