Better, Faster, Funner - Part 1

I've recently started working on a new web app project using all sorts of latest-and-greatest techniques and libraries, needless to say I choose Perl to build it out. The architecture of this application is different from what I'm used to but I like the direction I'm headed, everything seems to be in order, so I'd like to share my thoughts, struggles and accomplishments.

The WAF (web app framework) I choose to develop around this project is Dancer, although I've been away from Dancer and its community since before version 1 was officially released I decided to use it because, quite-frankly, it has nice and robust documentation, sensible defaults, and does exactly what I need it to with minimal code. You may have noticed that I neglected to mention the framework's plugin ecosystem, which IMHO is not preferred for large/growing projects.

The Hub

I've designed the application main module (root class) to be "the source of truth" for the entire application. This means that the root class is responsible for providing access to configuration options in an object-oriented fashion, allows switching between configuration profiles, writing to disk and providing absolute path information using Self::Dir. Some hackery and caching is needed to convince Dancer to switch configuration profiles during runtime.

The 2-Step (Not the Dance)

Like many other wishful thinkers I wanted the app to be completely self-contained and deploy-able with minimal fuss. After days of tinkering and rethinking botched deployments I came up with a simple 2-step system for deploying the app based on my typical work-flow.

I created two Perl scripts in the app root, setup-server and setup-application, both of which should be self-explanatory but allow me to elaborate. The requirements for the scripts are as follows:

The setup-server script should be executed first in the install process, it only requires that Perl 5.8+ be installed. I stringifies lists (arrays) of dependencies (system dependencies (build-essential, cpanm, etc), system cpan dependencies, local dependencies, system commands, etc) to be installed/executed in a particular order. This ensures that the server has the requirements need to run the next script, setup-application.

The setup-application script creates config files for various servers (web servers, databases, etc) and installs them in the assigned locations then starts/restarts the respective daemons. The magic CPAN library that makes creating, starting, stopping, and restarting services fun and stress-free is Ubic (especially when ran as root). Ubic is just pure awesome, it allows me easily add services to my application during a development iteration and have it just work when I re-deploy the app. I install each service to be executed via Ubic::Service::SimpleDaemon which has the added benefit of being automatically restarted on failure.

Stuck Between a Rock and a Hard Place

I've recently started evaluating libraries to provide form rendering, validation, general OO, and data storage.

Because I like using Validation::Class (which I authored) so much, for me the obvious choice was Validation::Class::Plugin::FormFields (which I also authored) though it has a sort of rigidness and inflexible to it, the type rigidness that can be found in all form rendering tools that rely on templating system like Template::Toolkit.

So I decided to use Validation::Class with ::Plugin::FormFields anyway and refactor it to make it better. I came to the conclusion some time ago that rendering forms in their entirety is too complex and specific to get right for all or even most use-cases, and an admirable compromise would be to provide an easy and intuitive interface to process and render form elements/fields individually.

So what does a better HTML form rendering engine look like? The following are code samples of the refactored V::C plugin I'm working on:

# $model is your V::C class instance

my $form = Validation::Class::FastForm->new(
    model => $model
);

# renders the login field as an HTML5 form input
# with data-minlength/maxlength, placeholder, etc
# and output the result

$form->render('login', 'text');
$form->render('login', 'email');
$form->render('login', 'date');
$form->render('login', 'number');

# alter you form field using css/xpath selectors
# and output the result

$form->process('login', 'text')
->change('input@type'  => 'password')
->change('input@title' => 'Please enter something here')
->data;

$form->dataset('user_type', [
    { text => 'Buyer',  value => 'buyer' },
    { text => 'Seller', value => 'seller'},
]);

$form->render('user_type', 'radio');
$form->render('user_type', 'checkbox');
$form->render('user_type', 'select');

$form->process('user_type', 'radio')
->change('select@multiple' => 'Yes')
->data;

These are working examples (currently) and hopefully give you a visual of the direction I'm heading in. If you feel the API for this form rendering engine is simple and easy-to-use, thanks, this simplicity was born out of frustration and providing this simplicity is bringing even more frustration.

I knew from the start I wanted to inline the templates and allow them to be extended and overridden per instance. I started out using HTML::Zoom which is nice but has some limitation that I wasn't willing to patch to move forward. Some of those limitations were failure to parse simple xpaths/css-selectors, failure to handle null and undef in a DWIM fashion, and constantly blowing-up with difficult to trace error messages.

I then refactored the code to use Template::Semantic which requires XML::LibXML which is not always the easiest to install via CPAN. Template::Semantic is great at handling null and undef in a DWIM fashion but lacks the chainable goodness once utilized in HTML::Zoom. Template::Semantic also doesn't die with roaming error messages. One thing that is extremely annoying is that it doesn't automatically append pushable attributes (like class, etc), and it doesn't create missing attribute nodes on-the-fly either. ARGGGHHH.

So I wrote Validation::Class::FastForm as a subclass of Template::Semantic which uses a role that provides a chainable API and formats selectors and vars so that they append pushable attributes and create non-existent attribute nodes. YAYYY.

The only problem now is every transformer is a coderef (like HTML::Zoom) which makes processing slower.

-Al

3 Comments

The architecture of this application is different from what I'm used to but I like the direction I'm headed, everything seems to be in order, so I'd like to share my thoughts, struggles and accomplishments.
I think it would help readers a lot if you could give some idea or a hint of what the application does, otherwise the article is really hard to get into.
@Ben, its a web application (framework), its based on Dancer (which I mentioned) and simply talks about an approach I took towards deployment, template-rendering, form-handling, and configuration management.
Yeah, that is exactly what I mean; you give a list of technical mumbo-jumbo and nowhere do you give any idea of what the application does, which is what makes the article so extremely confusing to read.

At one point my aunt had some kind of very serious dental problems, and she would go on and on and on about this saga of mistreatments and errors and illnesses caused by the dental treatment, but for some reason she would never tell me what on earth the original treatment had been. So for months and months and years and years, every time I saw my aunt, I had to listen to these endless complaints about only-hinted-at dental treatments, and I got more and more bored and exasperated, because she never told me the most vitally important part of the story: what was wrong with her teeth in the first place.

If you don't want to tell what the actual application does, because it's super-top-secret or something, you could always just make something up, so the reader has some kind of handle on what you are talking about. For example "I made a surf movie review site where people can vote for their favourite surfing action, Jan Michael Vincent in 'Big Wednesday' or Patrick Swayze in 'Point Break'". Anything is better than nothing. By deliberately skirting around what the application actually does, you've ended up with a post which nobody can understand.


Leave a comment

About Al Newkirk

user-pic ... proud Perl hacker, ask me anything!