Thursday, July 8, 2010

The beauty of Moose: Enforcing an interface

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 ;-)

No comments:

Post a Comment