Medium-Scale Dancer, Part 1: Modules

Or: How to Get Beyond Small-Scale in Dancer Application Design

When you generate a Dancer app with dancer2 -a App, you get a very nicely structured and useful starting point. As long as your app remains fairly small, you can work with this structure as-is clear through to completion. In that sense, the generated app is production-ready.

The problem comes when you get to thousands of lines of Perl code, dozens of templates, and dozens of routes, stretching the stock app design beyond its natural limits. This series of articles gives you one way to extend beyond this base.

I say "one way" because Dancer is a policy-free web application framework. Its designers purposely avoid making whole classes of design decisions for you. That means that Dancer not only doesn’t force you to design your app the "right way," by its creators' lights, the framework rarely even gives hints about what the right way might be. Dancer prefers to present itself as a box of tools, which you are free to use as you see fit.

The design presented here is working quite well for us, so we think it is worthy of emulation, but we do not expect that it will work for all medium-scale web applications. Think of it as a mutually consistent collection of design ideas, rather than a prescription of the One True Way to design a Dancer application.

The Generated Files

When you say dancer2 -a App at the command line, you get a few dozen files. Several of these files contain Perl code, but only one of those is interesting for the purposes of this article.

We're setting bin/app.psgi and public/dispatch.* aside as "uninteresting" here because we recommend that you leave these files as-is. None of your app-specific Perl code should go in these files.

We're also ignoring Makefile.PL since it's part of the app's build system, not part of the app proper.

That only leaves lib/App.pm. For small-scale apps, simply piling all of your Dancer-related app code into this file is just fine. Up to about one or two thousand lines of code, you can just use it as-is, for the same reason that a thousand-line monolithic Perl script is fine in other contexts, too.

Extending the Stock Scheme

One of the nicest things about Dancer is that the generated startup code adds the app's lib directory to the Perl module path. That leads to an obvious solution to the problem of too much code in a single file: break it out into other *.pm files in the lib directory. But how, exactly?

I recommend following the example of CPAN, with a structure like the following:

lib/App.pm                 # your app's main module
lib/App/MajorFeature.pm    # implementation of a major app feature
lib/App/OtherFeature.pm    # as many feature impl files as you need
lib/App/API.pm             # if your app has a REST API, put it here
lib/App/Const.pm           # maybe you have some app-wide constants
lib/App/Utility.pm         # every big app has a "junk drawer" module

Then in lib/App.pm, you use all of these modules:

use App::MajorFeature;
use App::OtherFeature;
use App::API;
use App::Const;
use App::Utility;

How you design these modules is up to you. The standard rules for well-structured Perl application design apply.

There is one Dancer2-specific thing to beware of here, though: you must set the appname property on the sub-modules. If you don't, each sub-module will be considered a separate Dancer2 application, which puts unwanted barriers between the modules.

(Dancer1 didn't have this requirement because it didn't have any concept of multiple apps within a single Dancer instance.)

When it comes time to break lib/App.pm up into multiple modules, I recommend that you move almost all Perl code into one of these modules, leaving only application initialization code in the top-level module.

(This, by the way, is why we don't need to modify bin/app.psgi: since it pretty much just loads Dancer and then calls lib/App.pm, there is no point in splitting app initialization code across the two files. Let bin/app.psgi remain a tiny little glue file that exists merely to make PSGI happy.)

Your initial refactoring of lib/App.pm will likely leave all the route definitions behind, which is fine because refactoring the routes is the subject of the next article in this series.

Author

This article has been written by Warren Young for the Perl Dancer Advent Calendar 2016.

Copyright

© 2015, 2016 by Educational Technology Resources, licensed under Creative Commons Attribution-ShareAlike 4.0