Application Design Techniques

Happy 3 December! Today, I'm going to talk about some techniques for making your Catalyst applications as maintainable (and enjoyable) as possible. These are all techniques I use in my applications daily, so don't think that this is just some theoretical nonsense that I think might be fun to write an article about :)

Note that most of the code below is paraphrased for readability. If you use $self in a method, you have to write my $self = shift; at the top, of course. The same goes for $c in actions. You already know this, so there's no point in hurting your eyes with it here :)

Sugaring up the stash

Typing $c->stash->{foo} all the time can get tiring. It's not easy to type, it's not pretty to look at, and it's easy to misspell foo. In controllers where I refer to a few stash keys frequently, I usually write some quick accessor method like this:

   package My::Controller::Of::Some::Sort;
   use base qw/Catalyst::Component::ACCEPT_CONTEXT Catalyst::Controller/;

   sub foo {
       my $self = shift;
       $self->context->stash->{foo} = shift if @_;
       return $self->context->stash->{foo};
   }

Then I can easily get and set foo:

   sub base :Chained ... {
       if(!$self->foo){ $self->foo(42) }
   }

   sub action :Chained ... Args(0)
       my ($self, $c) = @_;
       $self->foo($c->req->params->{foo});
   }

This all subclasses nicely, so you can write the accessor once, and then inherit it when necessary.

Base controllers

If you have multiple controllers that do something similar, you should consider factoring out the common elements into a base controller. I recently wrote an application where a few controllers had actions that accepted two arguments in the URL, a user id and a request id. I initially started with something like:

   sub begin :Private {
     my ($self, $c, $user, $request);
     $c->stash->{user} = $c->model('Users')->inflate($user);
     # same for request
   }

   sub action :Local Args(2) {
     my ($self, $c) = @_;
     $c->stash->{user}->do_something_with($c->req);
   }

I then added my accessors as above, to save a bit of typing. But then, another controller needed the same begin action. So, I factored it out:

  package MyApp::ControllerBase::RequestId;
  use base 'Catalyst::Controller';

  sub begin :Private { ... }

Then I subclassed it for my actual controllers:

  package MyApp::Controller::Foo;
  use base 'MyApp::ControllerBase::RequestId';

  sub action :Local Args(2) {
    $self->user->do_something_with($c->req);
  }

  sub another_action :Local Args(2) {
    $self->request->mark_as_accepted_by($self->user);
  }

Very clean. There's no reliance on strings indexing into hashes, and there's no repeated code.

(BTW, the reason I put user and request into $c->stash instead of into $self is because we show a message like "Thank you, $user->name, for approving request $request->uuid". The begin action that inflates and stashes the URL params makes the both the template and the controller eaiser to work with.)

Actions from base controllers

You can do more than inherit a common begin action, though. Any action can be inherited, and it conveniently shows up in the URL namespace of the consuming class. As an example:

  package MyApp::ControllerBase::ThingBase;
  use base 'Catalyst::Controller';

  sub delete :Local {
    $self->things->delete;
  }

  sub list :Local {
    $c->stash->{things} = [$self->things];
  }

(As an aside, note that this superclass calls into its subclass when it does $self->things, so it's really a role and not a superclass.)

Then we can inherit from the class and get actions that act on things:

  package MyApp::Controller::Foos;
  use base 'MyApp::ControllerBase::ThingBase';

  sub things { return $c->model('Foos')->get_everything }

and

  package MyApp::Controller::Bars;
  use base 'MyApp::ControllerBase::ThingBase';

  sub things { return $c->model('Bars')->get_everything }

Now my application has four actions:

  /bars/delete
  /bars/list
  /foos/delete
  /foos/list

And these all do what you would expect.

We can improve this a bit further by pulling the things method up to a superclass:

  package MyApp::ControllerBase::ThingBase;
  use base qw/Catalyst::Component::ACCEPT_CONTEXT Catalyst::Controller/;

  sub things {
    my $c = $self->context;
    return $c->model($self->{thing_source})->get_everything;
  }

  sub delete :Local {
    $self->things->delete;
  }

  sub list :Local {
    $c->stash->{things} = [$self->things];
  }

Here we inherit from Catalyst::Component::ACCEPT_CONTEXT to get $self->context (that way we can get $c without passing it around to everything), and we add a things method that uses $self->{thing_source} as the model. $self->{thing_source} is specified by:

  __PACAKGE__->config(thing_source => 'Whatever') 

in the subclasses:

  package MyApp::Controller::Foos;
  use base 'MyApp::ControllerBase::ThingBase';

  __PACKAGE__->config(thing_source => 'Foos');




  package MyApp::Controller::Bars;
  use base 'MyApp::ControllerBase::ThingBase';

  __PACKAGE__->config(thing_source => 'Bars');

The end result is the same as our original example, but this is a little bit less code.

The real advantage is that if I want to add a new feature (maybe "delete_inactive_users"), I only need to add it to the base controller. The subclasses just "pick it up", and it can be customized by configuration options.

Thin controllers

There will be another article this month on Rails-style "mvC" versus real MVC design, but for now I'll just briefly explain the concept.

You'll notice above that my example Catalyst actions above look like:

  sub approve_request :Local Args(2) {
    my ($self, $c) = @_;
    $self->request->set_status_to_approved;
  }

This is not abbreviating for the sake of an article; this is really what my code looks like. My goal is for there to be no code in the controller. Obviously that never happens, but I try to get as close as possible. The only things I do in the controller is read data from $c->req into something that makes sense for passing to my model:

  my $foo = $c->model('Foos')->lookup({ food => $c->req->params->{food} });

Whatever work is required to lookup a foo by its food is handled completely outside of Catalyst. This way I can easily write tests, and use the code in other applications. (We save a lot of development time by writing common code that can be subclassed for different clients or interfaces.)

The rest of the action is something like preparing $foo for display to the user:

  $c->stash->{foo} = $foo;

(Note that formatting $foo is then handled in the View class.)

I can also mutate $foo, if the action is verby:

  $foo->change_in_the_way_that_the_user_wants

That's it. Catalyst is a module for gluing my backend modules to the web. Nothing more.

SEE ALSO

If you are looking for more Catalyst reading material, check out my new Catalyst book at:

http://www.packtpub.com/catalyst-perl-web-application/book

Or, my open-enrollment Catalyst training class:

http://www.stonehenge.com/open_enrollment.html

AUTHOR

Jonathan Rockway <jrockway@cpan.org>

Infinity Interactive, Inc http://www.iinteractive.com.