Code refactoring gotchas?
Hi All, I'm just starting with Dancer, pointers to applicable documentation are more than welcome if you don't have time to give an extended answer. I sometimes am in the need of refactoring code that should be shared between different routes. As an example, I could have a POST route '/do-operation' that has to perform some operations and then generate a page exactly as if a GET route '/default' had been called. I understand that it's not possible (not advisable) to call a route from another route, so the obvious solution that comes to mind is to create a function that encapsulates all that the '/default' route action is supposed to do, then call it from the two different routes: sub default { # ... } get '/default' => \&default; post '/do-operation' => sub { # do whatever I need, then... return default(); } Is this approach save and future-proof? In particular, what are the constraints of using the different Dancer functions - e.g. var, session, splat, ... - inside a sub that isn't "installed" as a route action in the way all the examples report? To give a bit of perspective to my concern, I heard a lot about Devel::Declare and related stuff that plug in during the parsing phase, and I wouldn't like to incur in problems by not adhering to some "best practice", whichever it is (e.g. use HTTP redirections to force the browser to send a new request for '/default', but I would like to avoid this). Thank you, Flavio.
Flavio Poletti <polettix@gmail.com> writes:
I'm just starting with Dancer, pointers to applicable documentation are more than welcome if you don't have time to give an extended answer.
I sometimes am in the need of refactoring code that should be shared between different routes. As an example, I could have a POST route '/do-operation' that has to perform some operations and then generate a page exactly as if a GET route '/default' had been called.
I understand that it's not possible (not advisable) to call a route from another route, so the obvious solution that comes to mind is to create a function that encapsulates all that the '/default' route action is supposed to do, then call it from the two different routes:
Generally speaking, it is a good idea to encapsulate the logic of your application outside of the routes entirely, in another object. Then, have only a thin layer of code in the routes that does web-specific things, calls through to your application, and then presents the results in a web-specific way. That way you can reuse your application logic with other interfaces, as well as using standard Perl OO techniques, etc, to ease things like refactoring and code reuse. Finally, for common web things like validation, encapsulate: write a helper library (or maybe plugin) that captures what you need to do, and use that from inside the routes. (Except the rare things that are truly universal, in which case use the mechanisms that Dancer provides to hook in before every request.) ...at least, so my experience suggests. Daniel -- ✣ Daniel Pittman ✉ daniel@rimspace.net ☎ +61 401 155 707 ♽ made with 100 percent post-consumer electrons
Hi Daniel, thank you for sharing your experience. Have you got thoughts and/or suggestions about using the different Dancer functions - var, session, splat, ... - outside of route subs and inside other objects? Or are you suggesting that Dancer-specific functions should be called only inside the route subs in the thin layer and the implementation part should be Dancer-agnostic? This still leaves space to refactoring in the web-specific part. Now that I think about it, why "another" object? I.e. is there a "one" object in Dancer? Last question... I haven't found much documentation about the Plugins and how they are supposed to be written/used, but I saw that others use them or discuss them in the mailing list. Did I miss the right document? Thank you again, Flavio. On Fri, Sep 10, 2010 at 12:40 PM, Daniel Pittman <daniel@rimspace.net>wrote:
Flavio Poletti <polettix@gmail.com> writes:
I'm just starting with Dancer, pointers to applicable documentation are more than welcome if you don't have time to give an extended answer.
I sometimes am in the need of refactoring code that should be shared between different routes. As an example, I could have a POST route '/do-operation' that has to perform some operations and then generate a page exactly as if a GET route '/default' had been called.
I understand that it's not possible (not advisable) to call a route from another route, so the obvious solution that comes to mind is to create a function that encapsulates all that the '/default' route action is supposed to do, then call it from the two different routes:
Generally speaking, it is a good idea to encapsulate the logic of your application outside of the routes entirely, in another object.
Then, have only a thin layer of code in the routes that does web-specific things, calls through to your application, and then presents the results in a web-specific way.
That way you can reuse your application logic with other interfaces, as well as using standard Perl OO techniques, etc, to ease things like refactoring and code reuse.
Finally, for common web things like validation, encapsulate: write a helper library (or maybe plugin) that captures what you need to do, and use that from inside the routes.
(Except the rare things that are truly universal, in which case use the mechanisms that Dancer provides to hook in before every request.)
...at least, so my experience suggests. Daniel
-- ✣ Daniel Pittman ✉ daniel@rimspace.net ☎ +61 401 155 707 ♽ made with 100 percent post-consumer electrons _______________________________________________ Dancer-users mailing list Dancer-users@perldancer.org http://www.backup-manager.org/cgi-bin/listinfo/dancer-users
Hi Flavio and everyone, i am in a similar thinking process. First, your dancer app actually does come in its own package/object. Second, to make Flavio's question more concrete: I am currently restructuring and put most of the code in another object outside the actual dancer app. So far it was very convenient to use Dancer's debug and log functions. Is there any particular good way to log to Dancer from my other object? thanks maurice On Fri, Sep 10, 2010 at 7:37 AM, Flavio Poletti <polettix@gmail.com> wrote:
Hi Daniel, thank you for sharing your experience. Have you got thoughts and/or suggestions about using the different Dancer functions - var, session, splat, ... - outside of route subs and inside other objects? Or are you suggesting that Dancer-specific functions should be called only inside the route subs in the thin layer and the implementation part should be Dancer-agnostic? This still leaves space to refactoring in the web-specific part. Now that I think about it, why "another" object? I.e. is there a "one" object in Dancer? Last question... I haven't found much documentation about the Plugins and how they are supposed to be written/used, but I saw that others use them or discuss them in the mailing list. Did I miss the right document? Thank you again, Flavio.
On Fri, Sep 10, 2010 at 12:40 PM, Daniel Pittman <daniel@rimspace.net> wrote:
Flavio Poletti <polettix@gmail.com> writes:
I'm just starting with Dancer, pointers to applicable documentation are more than welcome if you don't have time to give an extended answer.
I sometimes am in the need of refactoring code that should be shared between different routes. As an example, I could have a POST route '/do-operation' that has to perform some operations and then generate a page exactly as if a GET route '/default' had been called.
I understand that it's not possible (not advisable) to call a route from another route, so the obvious solution that comes to mind is to create a function that encapsulates all that the '/default' route action is supposed to do, then call it from the two different routes:
Generally speaking, it is a good idea to encapsulate the logic of your application outside of the routes entirely, in another object.
Then, have only a thin layer of code in the routes that does web-specific things, calls through to your application, and then presents the results in a web-specific way.
That way you can reuse your application logic with other interfaces, as well as using standard Perl OO techniques, etc, to ease things like refactoring and code reuse.
Finally, for common web things like validation, encapsulate: write a helper library (or maybe plugin) that captures what you need to do, and use that from inside the routes.
(Except the rare things that are truly universal, in which case use the mechanisms that Dancer provides to hook in before every request.)
...at least, so my experience suggests. Daniel
-- ✣ Daniel Pittman ✉ daniel@rimspace.net ☎ +61 401 155 707 ♽ made with 100 percent post-consumer electrons _______________________________________________ Dancer-users mailing list Dancer-users@perldancer.org http://www.backup-manager.org/cgi-bin/listinfo/dancer-users
_______________________________________________ Dancer-users mailing list Dancer-users@perldancer.org http://www.backup-manager.org/cgi-bin/listinfo/dancer-users
Hi Maurice and all, taking a look at the code, there does not seem to be any reason why the various Dancer's functions cannot be called outside route subs. With reference to separated modules, it seems that the correct way to go is importing Dancer's syntax via: use Dancer ':syntax'; and then use the various convenience functions. Is this actually the only thing that is needed? Cheers, Flavio. On Fri, Sep 10, 2010 at 2:04 PM, Maurice Mengel <mauricemengel@gmail.com>wrote:
Hi Flavio and everyone,
i am in a similar thinking process.
First, your dancer app actually does come in its own package/object.
Second, to make Flavio's question more concrete: I am currently restructuring and put most of the code in another object outside the actual dancer app. So far it was very convenient to use Dancer's debug and log functions. Is there any particular good way to log to Dancer from my other object?
thanks maurice
On Fri, Sep 10, 2010 at 7:37 AM, Flavio Poletti <polettix@gmail.com> wrote:
Hi Daniel, thank you for sharing your experience. Have you got thoughts and/or suggestions about using the different Dancer functions - var, session, splat, ... - outside of route subs and inside other objects? Or are you suggesting that Dancer-specific functions should be called only inside the route subs in the thin layer and the implementation part should be Dancer-agnostic? This still leaves space to refactoring in the web-specific part. Now that I think about it, why "another" object? I.e. is there a "one" object in Dancer? Last question... I haven't found much documentation about the Plugins and how they are supposed to be written/used, but I saw that others use them or discuss them in the mailing list. Did I miss the right document? Thank you again, Flavio.
On Fri, Sep 10, 2010 at 12:40 PM, Daniel Pittman <daniel@rimspace.net> wrote:
Flavio Poletti <polettix@gmail.com> writes:
I'm just starting with Dancer, pointers to applicable documentation
are
more than welcome if you don't have time to give an extended answer.
I sometimes am in the need of refactoring code that should be shared between different routes. As an example, I could have a POST route '/do-operation' that has to perform some operations and then generate a page exactly as if a GET route '/default' had been called.
I understand that it's not possible (not advisable) to call a route from another route, so the obvious solution that comes to mind is to create a function that encapsulates all that the '/default' route action is supposed to do, then call it from the two different routes:
Generally speaking, it is a good idea to encapsulate the logic of your application outside of the routes entirely, in another object.
Then, have only a thin layer of code in the routes that does web-specific things, calls through to your application, and then presents the results in a web-specific way.
That way you can reuse your application logic with other interfaces, as well as using standard Perl OO techniques, etc, to ease things like refactoring and code reuse.
Finally, for common web things like validation, encapsulate: write a helper library (or maybe plugin) that captures what you need to do, and use that from inside the routes.
(Except the rare things that are truly universal, in which case use the mechanisms that Dancer provides to hook in before every request.)
...at least, so my experience suggests. Daniel
-- ✣ Daniel Pittman ✉ daniel@rimspace.net ☎ +61 401 155 707 ♽ made with 100 percent post-consumer electrons _______________________________________________ Dancer-users mailing list Dancer-users@perldancer.org http://www.backup-manager.org/cgi-bin/listinfo/dancer-users
_______________________________________________ Dancer-users mailing list Dancer-users@perldancer.org http://www.backup-manager.org/cgi-bin/listinfo/dancer-users
Flavio Poletti <polettix@gmail.com> writes:
Have you got thoughts and/or suggestions about using the different Dancer functions - var, session, splat, ... - outside of route subs and inside other objects?
Or are you suggesting that Dancer-specific functions should be called only inside the route subs in the thin layer and the implementation part should be Dancer-agnostic?
That would generally be what I aim for, but as you found you can 'use Dancer ":syntax"' anywhere and it will just work. [...]
Last question... I haven't found much documentation about the Plugins and how they are supposed to be written/used, but I saw that others use them or discuss them in the mailing list. Did I miss the right document?
There isn't much more that I know of than the documentation in Dancer, but given that plugins are basically just a wrapper around exporting functions by name there isn't all that much more to know. Daniel -- ✣ Daniel Pittman ✉ daniel@rimspace.net ☎ +61 401 155 707 ♽ made with 100 percent post-consumer electrons
Daniel Pittman <daniel@rimspace.net> writes:
Flavio Poletti <polettix@gmail.com> writes:
Have you got thoughts and/or suggestions about using the different Dancer functions - var, session, splat, ... - outside of route subs and inside other objects?
Or are you suggesting that Dancer-specific functions should be called only inside the route subs in the thin layer and the implementation part should be Dancer-agnostic?
That would generally be what I aim for, but as you found you can 'use Dancer ":syntax"' anywhere and it will just work.
Actually, let me expand on this. I think it deserves a little more detail: The vars, session, and splat are effectively inputs to the back-end application, right? vars pass data around in a web-specific way, session persists data across independent web requests, and splat (like params) is part of parsing the user input. So, for splat and params I would expect to pass the relevant parts of those pools of data into my application functions. If the function needs to know the ID of the object, pass that as an explicit argument. Then when you need a command line (or MQ, or SOAP, or XMLRPC, or email, or ... whatever) "frob object N" tool you can just pass in the N without the application knowing anything about the "splat" or "params" methods.[1] 'vars' is *really* a web flow implementation detail. It allows passing data between your filters and routes along a chain; that should either modify state inside the application object, or simply end up as another regular parameter to the application in the final handler. 'session' is probably the hardest part of this: your application probably needs something akin to one of these, though my strong personal preference is to design the application to have storage of its own, then use the "web" session side to work around the fact that HTTP is not continuous. That is, my web session will contain only the reference material needed for the immediate purpose of loading my application session, and maybe the few extra bytes of web-specific state we want to maintain. This has the bonus of making it appropriate to use a session storage engine like Dancer::Session::Cookie — although that really isn't as secure as I would like it to be. If your application session storage keys are not appropriately secure you would definitely want to consider something better. Maybe I should try and find time to polish and release one of the versions I have sitting on my disk as a Dancer plugin... Finally, you didn't mention it by name, but the template stuff would also generally stay outside the core of my application, because that varies quite a lot. Most of the methods would return data structures, which would then get fed by the container into whatever template or presentation engine they want; the general idea is that the return value SHOULD[2] be in a form that you could represent with JSON, since that is a suitable common denominator for all the possible ways it could be presented. So, the output of that would feed into the variables part of the template stuff built in to Dancer and all. Anyway, I hope that clarifies how I see the roles of those various attributes in relation to your overall application. I should also add that for something small, or specialised, enough I wouldn't go to these lengths: if you are writing less than three hundred lines of code and templates then this would be too heavy, and it should just use the Dancer stuff as *the* API. Probably. Unless it expects to grow. :) Regards, Daniel Footnotes: [1] Another approach, since Dancer is wonderfully light, is to state that the "splat" and "params" *are* your back-end API, and then provide your own implementation when you need a CLI tool. Not too terrible an idea, actually, in some ways. [2] As in the RFC2119 version of SHOULD, which is effectively "do it this way, even if you don't think you should". -- ✣ Daniel Pittman ✉ daniel@rimspace.net ☎ +61 401 155 707 ♽ made with 100 percent post-consumer electrons
I tend to read the SHOULD part in RFC 2119 as "strive to do this, but we're open to hear your reasons if you don't" :-) Jokes apart, a big thank you for your explanation, hints and... time. All the best, Flavio. On Sat, Sep 11, 2010 at 6:43 AM, Daniel Pittman <daniel@rimspace.net> wrote:
Daniel Pittman <daniel@rimspace.net> writes:
Flavio Poletti <polettix@gmail.com> writes:
Have you got thoughts and/or suggestions about using the different Dancer functions - var, session, splat, ... - outside of route subs and inside other objects?
Or are you suggesting that Dancer-specific functions should be called only inside the route subs in the thin layer and the implementation part should be Dancer-agnostic?
That would generally be what I aim for, but as you found you can 'use Dancer ":syntax"' anywhere and it will just work.
Actually, let me expand on this. I think it deserves a little more detail:
The vars, session, and splat are effectively inputs to the back-end application, right? vars pass data around in a web-specific way, session persists data across independent web requests, and splat (like params) is part of parsing the user input.
So, for splat and params I would expect to pass the relevant parts of those pools of data into my application functions. If the function needs to know the ID of the object, pass that as an explicit argument.
Then when you need a command line (or MQ, or SOAP, or XMLRPC, or email, or ... whatever) "frob object N" tool you can just pass in the N without the application knowing anything about the "splat" or "params" methods.[1]
'vars' is *really* a web flow implementation detail. It allows passing data between your filters and routes along a chain; that should either modify state inside the application object, or simply end up as another regular parameter to the application in the final handler.
'session' is probably the hardest part of this: your application probably needs something akin to one of these, though my strong personal preference is to design the application to have storage of its own, then use the "web" session side to work around the fact that HTTP is not continuous.
That is, my web session will contain only the reference material needed for the immediate purpose of loading my application session, and maybe the few extra bytes of web-specific state we want to maintain.
This has the bonus of making it appropriate to use a session storage engine like Dancer::Session::Cookie — although that really isn't as secure as I would like it to be.
If your application session storage keys are not appropriately secure you would definitely want to consider something better. Maybe I should try and find time to polish and release one of the versions I have sitting on my disk as a Dancer plugin...
Finally, you didn't mention it by name, but the template stuff would also generally stay outside the core of my application, because that varies quite a lot.
Most of the methods would return data structures, which would then get fed by the container into whatever template or presentation engine they want; the general idea is that the return value SHOULD[2] be in a form that you could represent with JSON, since that is a suitable common denominator for all the possible ways it could be presented.
So, the output of that would feed into the variables part of the template stuff built in to Dancer and all.
Anyway, I hope that clarifies how I see the roles of those various attributes in relation to your overall application.
I should also add that for something small, or specialised, enough I wouldn't go to these lengths: if you are writing less than three hundred lines of code and templates then this would be too heavy, and it should just use the Dancer stuff as *the* API. Probably. Unless it expects to grow. :)
Regards, Daniel
Footnotes: [1] Another approach, since Dancer is wonderfully light, is to state that the "splat" and "params" *are* your back-end API, and then provide your own implementation when you need a CLI tool. Not too terrible an idea, actually, in some ways.
[2] As in the RFC2119 version of SHOULD, which is effectively "do it this way, even if you don't think you should".
-- ✣ Daniel Pittman ✉ daniel@rimspace.net ☎ +61 401 155 707 ♽ made with 100 percent post-consumer electrons _______________________________________________ Dancer-users mailing list Dancer-users@perldancer.org http://www.backup-manager.org/cgi-bin/listinfo/dancer-users
On Friday 10 September 2010 10:27:34 Flavio Poletti wrote:
I sometimes am in the need of refactoring code that should be shared between different routes. As an example, I could have a POST route '/do-operation' that has to perform some operations and then generate a page exactly as if a GET route '/default' had been called.
Personally, in that case I'd be inclined to redirect to /default after performing the appropriate operations; this also has the benefit of meaning that if a user hits refresh, they're not prompted to re-submit the POST request, and avoids any problems that them blindly clicking OK and re- submitting it could cause. -- David Precious <davidp@preshweb.co.uk> http://blog.preshweb.co.uk/ www.preshweb.co.uk/twitter www.preshweb.co.uk/linkedin www.preshweb.co.uk/facebook www.preshweb.co.uk/identica www.lyricsbadger.co.uk "Programming is like sex. One mistake and you have to support it for the rest of your life". (Michael Sinz)
participants (4)
-
Daniel Pittman -
David Precious -
Flavio Poletti -
Maurice Mengel