I must admit, I was reluctant to jump into Moose straightaway. I watched from the sidelines for a long time. Then, finally, I decided to jump in. Right now, I am only using pretty basic Moose features with none of the facilities provided by the various extensions in the MooseX:: namespace.
I am still working on writing economics experiments with Dancer “Yes, I know, I need to post some slides, I haven’t forgotten.”
I chose to implement every route handler, response/view and significant method as a role. To show what I mean, I am going to consider a simple contact form handler (so as not to bother you with the experimental economics specific details). Here is how the main class for the application looks:
package My::App;
use Moose;
use namespace::autoclean;
with 'My::Handler::get::contact_form';
with 'My::Handler::get::contact_form_submitted';
with 'My::Handler::post::contact_form';
with 'My::View::contact_form';
with 'My::View::contact_form_submitted';
with 'My::View::redirect::contact_form::submitted';
__PACKAGE__->meta->make_immutable;
1;
In this case, My::Handler::get::contact_form
is a role that supplies the get_contact_form
method which is wired using Dancer’s syntax helpers to be invoked when an HTTP GET request is made to the URL /contact_form
. get_contact_form
finishes by invoking view_contact_form
which is supplied by the role My::View::contact_form
.
All this looked pretty neat to me (although, it might be a completely insane way of doing things given that this is my first attempt to use Moose in a project). I like it because tweaking one aspect of the application can be achieved very simply by composing a different role in a subclass:
package My::App::Neater;
use Moose;
use namespace::autoclean;
extends 'My::App';
with 'Neater::Handler::post::contact_form';
__PACKAGE__->meta->make_immutable;
1;
However, I was bothered by the fact that I did not get an error until run time if I forgot to pull in a role that provided a required method. (The base application class for an experiment mixes in tens of such roles).
If I were programming in Java, I would have written an interface and have My::App
implement that. While I do not like Java’s verbosity, I do like interfaces. Fortunately, that can be achieved very easily, again, using Moose roles. Defining the interface is straightforward:
package My::Interface;
use Moose::Role;
use namespace::autoclean;
requires 'get_contact_form';
requires 'get_contact_form_submitted';
requires 'post_contact_form';
requires 'view_contact_form';
requires 'view_contact_form_submitted';
requires 'view_redirect_contact_sumitted';
1;
Then, I can simply add with 'My::Interface';
to My::App
:
package My::App;
use Moose;
use namespace::autoclean;
with 'My::Interface';
# lines composing the various roles
# omitted for testing
__PACKAGE__->meta->make_immutable;
1;
Note that I commented out all the with
lines to see what happens when I invoked the following short script:
#!/usr/bin/perl
use strict; use warnings;
use lib 'lib';
use My::App;
my $x = My::App->new;
And, It Works! I get the following error message:
C:\Temp> roles
'My::Interface' requires the methods 'get_contact_form',
'get_contact_form_submitted', 'post_contact_form',
'view_contact_form', and 'view_redirect_contact_form_submitted'
to be implemented by 'My::App' …
followed by a long stack trace making for a very Java-like experience ;-)