Probably the most popular Perl web framework is Catalyst now, it is also the web framework I know the best - this is why I choose it as the point of reference for the analysis below.
my ( $self, $c, ... ) = @_
, not very DRY. The $c
parameter was dragged everywhere as if it was plain old procedural programming.
At some point I counted how often methods from the manual use the controller object versus how often they use the context object which contains the request data - the result was 117 and 38 respectively. Many people commented that their code often uses the controller object. It's hard to comment on this until the code is made public, my own experience was very close to that show by the manual, but this disproportion is only an illustration. The question is not about switching $c with $self. The question is why the request data, which undeniably is in the centre of the computation carried on in controllers, has to be passed around via parameters just like in plain old procedural code instead of being made object data freely available to all methods?
My curiosity about this was never answered in any informative way until about year ago I finally got a reply in private communication from some members of the core team - they want the controller object to be mostly immutable, and that of course would not be possible if one of it's attributed contained data changing with each new request. Immutable objects are a good design choice and I accepted this answer but later I though: Wait a minute - what if we recreated the controller object anew with each request? Then it could hold the request data and still be immutable for his whole life time. Catalyst controllers are created at the starup time, together with all the other application components (models and views) and changing that does not seem feasible - that's why I decided to try my chances with writing a new web framework.
I have tried many Perl web frameworks but I found only one more that uses controllers in request scope - it is a very fundamental distinguishing feature of WebNano. It's not only about reducing the clutter of repeatable parameter passing - I believe that putting object into their natural scope will fix a lot of the widely recognized problems with the Catalyst stash and data passed through it. In procedural programming it is not a controversial rule to put your variable declarations into the narrowest block that encompasses it's usage - the same should be true for objects. Sure using the same controller to serve multiple request is a bit faster then recreating it each time - but that gain is not that big - Object::Tiny on one of the tester machines could create 714286 object per second and even Moose - the slowest framework tested there can create more then a 10000 objects per second. Eliminating a few of such operations, in most circumstances, is not worth compromising on the architecture. By the way I also tested these theoretical estimations with more down to earth ab benchmarks for a trivial application serving just one page - WebNano came out as the fastest framework tested. This is still not very realistic test - but should be enough to show that the cost introduced by this design choice does not need to be big.
Catalyst started the process of decoupling the web framework from the other
parts of the application, with Catalyst you can use any persistence layer for
the model and any templating library for the view. The problem is that the
model
and view
methods in Catalyst become rather empty
- there is no common behaviour among the various libraries - so all these
methods do is finding the model or view by it's name. For WebNano I decided
that ->Something
is shorter and not less informative then
->model('Something')
- and I got rid of these methods.
The other thing that I decided not to add to WebNano is initialization of components. Component initialization is a generic task and it can be done in a similar way for all kinds of programs and the library that does that job does not need to know anything about the web stuff. At CPAN there are such libraries. For my limited experiments MooseX::SimpleConfig was very convenient, for more complex needs Bread::Board seems like a good choice. This initialization layer needs to know how to create all the objects used by the application - but you don't need any kind of WebNano adapter for them.
Writing a dispatcher is not hard - it becomes hard and complex when you try to write a dispatching model that would work for every possible application. I prefer to write a simple dispatcher covering only the most popular dispatching scenarios - and let the users of my framework write their own specialized dispatching code for their specialized controllers. With WebNano this is possible because these specialized dispatchers don't interfere with each other.
I also believe that this will make the controller classes more encapsulated - thus facilitating building libraries of application controllers.
I think with this type of inheritance it would be more natural to publish applications to CPAN, because they could be operational without any installation. A user could run them directly from @INC and only later override the configuration or templates as needed.
It is well known that using Moose generates some significant startup overhead. For web applications running in persistent environments - this does not matter much because that overhead is amortized over many requests, but if you run your application as CGI - this suddenly becomes important. I was very tempted to use Moose in WebNano - but even if CGI is perceived so passe now - it is still the easiest way to deploy web applications and also one that has the most widespread support from hosting companies. Fortunately using MooseX::NonMoose it is very easy to treat any hash based object classes as base classes for Moose based code, so using WebNano does not mean that you need to stick to the simplistic Object Oriented Framework it uses.
The plan is to make WebNano small, but universal, then make extensions that will be more powerful and more restricted. I think it is important that the base platform can be used in all kinds of circumstances.
$c
), and I think it's inheritable templates are
nice at deployment - you don't need to write your own templates to see it working,
later you can easily override the defaults. There is a bit more dispatching
code - but thanks to that the result object is being retrieved from the model in
only one place. In Catalyst this could also be done now by using the chained
dispatching. In the
examples directory
there are four variations on this CRUD theme - I am still not decided about
what is the optimal one.
The surprising thing when converting Nblog from Catalyst to WebNano was how little I missed the rich Catalyst features, even though WebNano has still so little code. I think it is a promising start.
Recently I discovered that many of my design choices are echoed in the publications by the Google testing guru Miško Hevery. My point of departure for the considerations above were general rules - like decoupling, encapsulation etc - his concern is testability - but the resulting design is remarkably similar. There is a lot of good articles at his blog, some were similar to what I already had thought over, others were completely new to me. I recommend them all.