Last year, I started experimenting with Dancer2 (one of the options on the table for our target framework when we rewrite much of our code).
Today, I've been updating that code to the HEAD of the current master branch. During the work I've done so far (last year and today), I've come across several obstacles which now lead me to bring up the following for discussion.
So, let me start by stating which problem I think we're looking to solve: we need a means to do request parameter handling, session management, pluggable authentication, routing, management, template handling and abstraction of a templating library.
During my experiments with Dancer2, I found that indeed it provides a framework with the desired properties. However, as with other frameworks, Dancer2 imposes its "framework structure" on applications. Normally this helps simplify the task of application development. Yet with the peculiarities in our code base, I'm finding that to implement required functionalities, I'm already working against the framework rather than with it:
1. Session management (cookies)
As part of the move from our current design to the Dancer2 framework, I'm aiming for setup.pl
to become completely separate applications, meaning that they require separate authentication cookies (to allow separate, concurrent logins into both apps from a single browser)
While Dancer2 does support multiple apps running on a single instance of its framework (such as required to run login.pl
from the same backend), it doesn't support - in its configuration file (config.yml) - that these apps use disjoint session management cookies. In order to support this use-case, I've had to add request hooks, changing the name of the session cookie on a per-request basis.
As LedgerSMB uses a rather unusual authentication scheme where the user+password+database name are required for login, our authentication scheme doesn't fit the standard set by the framework (Dancer2::Plugin::Auth::Extensible::*), I've ended up hacking "deep" into the internals of auth::extensible: our authentication couldn't be achieved without replacing the login templates and finding ways to make more parameters available than supported out of the box.
3. Templating library abstraction
While abstraction from templating libraries sounds nice, in practice it's cumbersome to switch templating libraries: on the Perl side the abstraction works fine to swap from one to another, but templating libraries usually use different templating "languages" (i.e. TemplateToolkit uses different directives and paradigms than XSLate). So, it seems that managing instantiation of the templating library is more important than abstracting it.
4. Route definition & dispatch
With the ability to define routes, we'll be able to detach URL structure from our internal code structure. Dancer2 has a nice DSL to support the definition of routes for non-webservice entry points. There are plugins available to extend the DSL with convenience keywords for definition of webservice entry points.
However, the syntactic sugar provided by the endorsed plugin (Dancer2::Plugin::REST) provides 1 new DSL keyword which is nothing more than a way to attach 4 functions to a resource (create, get, update, delete) providing a generated route for each; I see very little benefit to this plugin. In addition, this plugin doesn't support HTTP's content negotiation; instead, it expects the requested resource to have an "extension" to indicate the desired response encoding (e.g.: /users/1.json returns JSON, /users/1.xml returns XML, etc). This isn't "http-native" enough for me and thereby unacceptable for our use. Then there's the (not endorsed) Dancer2::Plugin::WebService; however, this too encodes the desired response encoding in the URL...
5. Framework configuration
Dancer2 supports running multiple "apps" on a single instance. We want to run multiple apps: our setup.pl
. Even: we want these multiple apps to run on different authentication domains (i.e. when a user is logged in in /login.pl
, we don't automatically want the user to be logged in into /setup.pl
too). During my experiments I found that this isn't something that Dancer2 supports in its configuration: it can be "tricked" to support it by installing the correct pre- and post-request hooks in the framework, but out-of-the-box, it's not there. I'm not sure what other areas this will affect in the future, but from the structure of the configuration, I'm imagining it'll be an issue in many places.
So what is our way forward then? Well, while we were working to merge the Multi Currency branch and stabilizing the 1.7 release (this process is still on-going), I've been looking around for solutions to these issues and I think the Perl library ecosystem has very nice options for almost all of the problems mentioned above, with the remark that there will obviously not be such a nice DSL as there will be with Dancer2. The following alternatives are available:
1. For session management using encrypted cookies, there's Session::Storage::Secure which works and is agnostic of underlying technology
2. For pluggable authentication, as we need to write our own authenticator anyway (and we actually already *have* that as a Plack middleware), I there's no effort if we can reuse that
3. For templating engine abstraction, well, I guess that with my line of reasoning above, there's little reason to have abstraction for templating engines at all (I mean, there *is* for a framework which needs to support many code bases, but there isn't for our specific use-case: our code base)
4. Route definition and dispatch (including extraction of route parameters) is available in a technology agnostic library Router::XS; while this library doesn't have the solid DSL that Dancer2 has, I'm expecting us to move more and more to Webservice entrypoints (either JSONRPC, GraphQL or REST) meaning that the DSL Dancer2 provides will be far less useful as stated above
5. Framework configuration could be handled through one of the regular YAML libraries, but even better - especially with inversion of control / dependency injection - we could turn to Beam::Wire and be able to deal with the most complex configurations by deferring configuration handling to sysadmins
Obviously, Dancer2 will be able to do request parameter handling. That's something that's supported today too: we already have Plack::Request to handle parameter parsing (both URL and body parameters).
So, after this long story, I'm wondering if moving to Dancer2 is worth it, or that we can simply stay on Plack with its middlewares and put the aforementioned modules to good use. Lets have your opinions and a good discussion on the matter so we lay a strong foundation for next steps for our project.
Robust and Flexible. No vendor lock-in.