FYI: Don’t judge the code in the following article part too harshly. It’s all
been written off-the-cuff. I have not yet tried to save it out into a final
app and make sure it runs. That will be part of the polishing passes to come.
The main thing I want comment on is the overall ideas presented, not the niggly
details.
I don’t mean that I do not welcome nit-picking, just that I prefer that you
keep it brief. Yes, I realize that the code probably doesn’t work as-is yet,
and yes, I realize that I probably have committed spelling and grammar
mistakes. Kindly just point them out, without discussing how this pile of
errors reflects on my ancestry. :)
Part 2: Route Definitions
-------------
By the time a Dancer app grows large enough that you want to start breaking it
up into multiple Perl modules, as in the previous article in this series,
you've probably also defined enough routes that you're starting to have
problems managing them all. Just as with the Perl code, Dancer lets us break up
the monolithic route definition set, too.
If you structured your app in the way recommended in the first part of this
series, each major feature of your web app is in its own Perl module. That Perl
module's name likely corresponds to some part of your app's URL scheme. Let's
say you're exposing the features of `App::MajorFeature` as `/mf` in URLs, with
sub-features underneath that.
If you extend the generated `lib/App.pm` file in the most obvious way, using
the simplest examples from the Dancer documentation, you might have a mess that
looks something like this:
get '/mf' => sub {
# Lots of Perl code to return the top-level MajorFeature view
};
get '/mf/subfeature' => sub {
# Implementation of a sub-feature of MajorFeature
};
post '/mf/subfeature' => sub {
# Maybe you need a way to add new subfeature objects
};
put '/mf/subfeature' => sub {
# And maybe also a way to edit existing subfeature objects
};
del '/mf/subfeature/:id' => sub {
# And a way to delete them, too
};
The first thing to fix here is that almost all of the Perl code implementing
each route handler should move to `lib/App/*.pm`. Ideally, each route handler
body should do nothing more than call a function in one of these modules:
get '/mf' => sub { App::MajorFeature::get(context); };
get '/mf/subfeature' => sub { App::MajorFeature::sub_feature(context);
};
post '/mf/subfeature' => sub { App::MajorFeature::add(context); };
put '/mf/subfeature' => sub { App::MajorFeature::modify(context); };
del '/mf/subfeature/:id' => sub { App::MajorFeature::remove(context); };
The `context()` function is a small wrapper I define at global scope within the
app:
sub context {
return {
config => config(),
request => request(),
session => session(),
conn => App::Utility::get_db_conn(),
etc => ...
};
}
That is, it just bundles up a bunch of Dancer objects into a hash for you. I
find this convenient, but perhaps you will want to use the DSL instead.
The URL scheme defined above is quite redundant. We can factor out that
redundancy in two stages.
First, Dancer has the awesome
[`prefix`](https://metacpan.org/pod/distribution/Dancer2/lib/Dancer2/Manual.pod#prefix)
feature, which lets us express the URL hierarchy directly in the code, without
repeating each element:
prefix '/mf' => sub {
get '/' => sub { App::MajorFeature::get(context); };
prefix '/subfeature' => sub {
get '/' => sub { App::MajorFeature::sub_feature(context); };
post '/' => sub { App::MajorFeature::add(context); };
put '/' => sub { App::MajorFeature::modify(context); };
del '/:id' => sub { App::MajorFeature::remove(context); };
};
};
Factoring out the `/mf` part was a net loss of 1 character per route with our
4-space indents, but factoring out `/subfeature` saved us a net 6 characters
per route, which really helps make the code easier to read.
You may find after rewriting your URL handlers this way that you see structural
patterns in the URLs that will lead you toward a better URL design. This can be
especially helpful in REST API design, which we will discuss in a later part of
this article series.
But to get back to our refactoring work, a second excellent feature of Dancer
lets us shorten those lines of code still further.
So far, we've been using explicitly-qualified function names. This is because
we want to use short function names within the modules (e.g. `Get()`) without
causing namespace collisions by exporting all of the functions. But in fact,
there is actually no need to expose the API of your modules outside the module
itself. Dancer doesn't care *where* you define the route handlers, just that
they're all defined by the time your caller wants to use them. In the previous
part of this article series, we said `use App::MajorFeature` and such within
`lib/App.pm`, so every one of our app's modules gets executed on startup. This
means that any code at global scope within these modules also runs at startup.
Therefore, we can move all of the route definitions above from `lib/App.pm` to
the end of `lib/App/MajorFeature.pm`:
prefix '/mf' => sub {
get '/' => sub { get(context); };
prefix '/subfeature' => sub {
get '/' => sub { sub_feature(context); };
post '/' => sub { add(context); };
put '/' => sub { modify(context); };
del '/:id' => sub { remove(context); };
};
};
Now all the function calls are made within the module itself, so we don't need
to qualify them.
If you are using my `context()` idea, you will have to move it out of
`lib/App.pm`, such as into the `App::Utility` module, exported by default:
package App::Utility {
use Dancer2 appname => 'App';
require Exporter;
use base qw(Exporter);
our @EXPORT = qw(context);
sub get_db_conn {
# do something useful here;
# not exported, since context->{conn} holds our return value
}
sub context {
return {
config => config(),
request => request(),
session => session(),
conn => get_db_conn(),
etc => ...
};
}
}
In the first part of this article series, we moved almost all of the Perl code
from `lib/App.pm` into a collection of tightly-scoped `lib/App/*.pm` modules.
In this second part, we moved most of the route definitions into those modules,
too, not only cleaning up `lib/App.pm`, but also shortening the definition of
those route handlers considerably by removing redundant code. All that should
be left in `lib/App.pm` is app-wide Dancer setup code, such has global hook
definitions.
In the next part, we will consider how this application restructuring affects
the design of other parts of the web app.
_______________________________________________
dancer-users mailing list
[email protected]
http://lists.preshweb.co.uk/mailman/listinfo/dancer-users