Dancer2 vs Plack::Request+Router::XS+Middlewares+TemplateToolkit+Beam::Wire ?
Hi, 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 and login.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 and setup.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. 2.Pluggable authentication 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 and login.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. Regards, -- Bye, Erik. http://efficito.com -- Hosted accounting and ERP. Robust and Flexible. No vendor lock-in.
Thanks Erik for taking the time to do this analysis. I've found Dancer2 to be one of the least opinionated and more flexible frameworks (compared with Mojolicious or Catalyst for example), so it's interesting to hear the difficulties you're running into. If, as your work seems to suggest, lsmb does not easily fit with Dancer2, I say we shouldn't force it. Using an established framework makes it easier for others to interact with our code base, through using common design patterns, but if we have to hack the framework or use it in non-standard ways, that advantage is lost. I wonder if we can extend Dancer2 to cover our needs, but your analysis suggests that would be quite invasive. I think we need to avoid, if possible, creating our own web framework from scratch, but we can perhaps enhance what we have. To pick out and respond to a few of your comments:
So, it seems that managing instantiation of the templating library is more important than abstracting it.
Agreed. I see no big advantage in abstracting the templating library.
With the ability to define routes, we'll be able to detach URL structure from our internal code structure.
This for me is one of the key improvements we can make to our code base - easier to maintain, test, understand, re-use. But we can achieve that - and perhaps achieve a more gradual evolution of our code base by way of...
Route definition and dispatch (including extraction of route parameters) is available in a technology agnostic library Router::XS
This looks pretty decent to me. Do you have any thoughts as to how easily we could adopt Router::XS? Does that start to provide a more gentle path to restructuring? We've discussed before the decision to evolve the code base, rather than starting afresh. I think that's right, so the idea of gradual improvements appeals to me. Best wishes, Nick
Hi Nick, On Wed, Jul 31, 2019 at 10:54 PM Nick Prater <nick@npbroadcast.com> wrote:
I've found Dancer2 to be one of the least opinionated and more flexible frameworks (compared with Mojolicious or Catalyst for example), so it's interesting to hear the difficulties you're running into.
If, as your work seems to suggest, lsmb does not easily fit with Dancer2, I say we shouldn't force it. Using an established framework makes it easier for others to interact with our code base, through using common design patterns, but if we have to hack the framework or use it in non-standard ways, that advantage is lost.
I wonder if we can extend Dancer2 to cover our needs, but your analysis suggests that would be quite invasive. I think we need to avoid, if possible, creating our own web framework from scratch, but we can perhaps enhance what we have.
Yes! I think we really should avoid creating our own web framework and we definitely should avoid coding it all from scratch by ourselves. (Which is why I was looking at the Perl ecosystem to see what it provides out of the box for us to build upon.) As to the question what problems can be solved by extending Dancer2, I think my answer would be: * To address the non-HTTP-ish nature of the endorsed REST plugin, we can write our own * To address the fact that our login functionality doesn't currently fit Dancer2::Plugin::Auth::Extensible, we could (try to) work with the author(s) to extend it to support our use-case * I guess the need for a DSL with respect to route definitions for REST resources will always remain highly limited (iow, with or without a framework doesn't make a big difference) * To address the framework configuration issue for the different cookies for different routes/apps: I guess that goes too deep into the framework to address on our own. I've mentioned this to "racke" (Stefan Hornburg) in our IRC channel when I worked with it, but reading the changelogs, I don't see anything which might have changed the situation.
To pick out and respond to a few of your comments:
So, it seems that managing instantiation of the templating library is more important than abstracting it.
Agreed. I see no big advantage in abstracting the templating library.
With the ability to define routes, we'll be able to detach URL structure from our internal code structure.
This for me is one of the key improvements we can make to our code base - easier to maintain, test, understand, re-use. But we can achieve that - and perhaps achieve a more gradual evolution of our code base by way of...
Route definition and dispatch (including extraction of route parameters) is available in a technology agnostic library Router::XS
This looks pretty decent to me.
Do you have any thoughts as to how easily we could adopt Router::XS? Does that start to provide a more gentle path to restructuring?
That's a nice dimension you're adding here to our evaluation. I hadn't thought about it that way. My first thought is that this *does* allow a more gradual migration of the code base to a modern structure as it separates the dispatch itself from the need to rewrite templates to be made consistent with Dancer's way of passing parameter values to templates and rewriting the code to be "Dancer2 code" instead of "PSGI code".
We've discussed before the decision to evolve the code base, rather than starting afresh. I think that's right, so the idea of gradual improvements appeals to me.
Ok. I might want to try and do a similar rewrite as the one I started with on setup.pl with Dancer2 with the components I described and see what effort is required to move the current code base a step closer to the target structure. I'll do that and report back when I have experience to share. Regards, -- Bye, Erik. http://efficito.com -- Hosted accounting and ERP. Robust and Flexible. No vendor lock-in.
On 7/27/19 7:13 PM, Erik Huelsmann wrote:
Hi,
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 <http://setup.pl> and login.pl <http://login.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 <http://login.pl> and setup.pl <http://setup.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.
Each of these apps should be able to have their own configuration directory so you can change the 'session_name'. Alternatively you could change it directly in the script (setup.pl / login.pl).
2.Pluggable authentication 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.
Where do you handle the authentication within LedgerSMB - I would like to take a look at it.
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.
I would like to have a generic template abstraction and instantation outside of Dancer2, e.g. for sending automated emails.
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...
I wouldn't say that that plugin is endorsed, it is rather a way to get a REST app working quickly. You could write a custom plugin based on a description file which creates the routes on startup (see https://metacpan.org/release/Dancer2-Plugin-Interchange6/source/lib/Dancer2/...). It should be also possible to set a variable for the response encoding and use it at the end of the processing. Regards Racke
5. Framework configuration Dancer2 supports running multiple "apps" on a single instance. We want to run multiple apps: our setup.pl <http://setup.pl> and login.pl <http://login.pl>. Even: we want these multiple apps to run on different authentication domains (i.e. when a user is logged in in /login.pl <http://login.pl>, we don't automatically want the user to be logged in into /setup.pl <http://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.
Regards, -- Bye,
Erik.
http://efficito.com <http://efficito.com/> -- Hosted accounting and ERP. Robust and Flexible. No vendor lock-in.
_______________________________________________ devel mailing list devel@lists.ledgersmb.org https://lists.ledgersmb.org/mailman/listinfo/devel
-- Ecommerce and Linux consulting + Perl and web application programming. Debian and Sympa administration. Provisioning with Ansible.
Hi Stefan, Thanks for taking the time to respond to my analysis! On Sun, Aug 4, 2019 at 1:14 PM Stefan Hornburg (Racke) <racke@linuxia.de> wrote:
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.
[ snip ]
1. Session management (cookies) As part of the move from our current design to the Dancer2
framework, I'm aiming for setup.pl <http://setup.pl> and
login.pl <http://login.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 <http://login.pl> and setup.pl <http://setup.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.
Each of these apps should be able to have their own configuration directory so you can change the 'session_name'. Alternatively you could change it directly in the script (setup.pl / login.pl).
Ok. From what you say, I understand that we can have the same source hierarchy as today. To that hierarchy, we would add 2 config directories (now I added only 1). In those 2 config directories, we store the config for running the "setup.pl" app and separately the "login.pl" app (after they have been rewritten to Dancer, of course). Then, with those two config directories, we can start these 2 dancer2 apps on a single Perl instance. However, back when I came to my original conclusion, I read all of the Dancer2 docs and nothing suggests that this can be done. Just now, I've re-read https://metacpan.org/pod/distribution/Dancer2/lib/Dancer2/Config.pod and the only way to run 2 Dancer2 apps is by starting 2 dancer2 (=perl) processes. Am I wrong?
2.Pluggable authentication As LedgerSMB uses a rather unusual authentication scheme where the
login, our authentication scheme doesn't fit the standard set by the
user+password+database name are required for 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.
Where do you handle the authentication within LedgerSMB - I would like to take a look at it.
The code is a bit convoluted, but was recently rewritten as a Plack middleware. Which is probably the best indication to what we want to achieve that we have available. Older versions had that scattered around the code base which was hard to follow/understand: https://github.com/ledgersmb/LedgerSMB/blob/master/lib/LedgerSMB/Middleware/...
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.
I would like to have a generic template abstraction and instantation outside of Dancer2, e.g. for sending automated emails.
That would indeed be absolutely great. However, for the scope of our own project, the templates are likely to be geared to our application anyway: most of the UI needs to be redeveloped when switching to another template processor. However, it would be nice if we could offer our users various different template processors for the templates they create.
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...
I wouldn't say that that plugin is endorsed, it is rather a way to get a REST app working quickly. You could write a custom plugin based on a description file which creates the routes on startup (see
https://metacpan.org/release/Dancer2-Plugin-Interchange6/source/lib/Dancer2/... ).
Ok. I think the route creation itself is the least of our problems, as route creation is likely to follow pretty standard patterns for which it's not really hard to develop our own dsl keyword. What's more interesting is to make sure the content is being "negotiated": when there's an Accept header available, that header should be used to determine the resource's representation as returned by the server. Ideally the framework deals with that requirement by taking a (non-stringy) response and serializing it into the desired format.
It should be also possible to set a variable for the response encoding and use it at the end of the processing.
I'm imagining a "book/1" resource defined as the Perl object { title => "Perl Best Practices", author => "Damian Conway" } to be returned as JSON when the Accept header prefers that (and the server has a serializer set up for it). Yet the same object gets returrned as an HTML document (a processed template) when the client prefers HTML or doesn't even mention JSON in the Accept header. Also, when content is being encoded according to the requirements as specified in the Accept header, I'd expect the framework to take care of setting headers like Vary header to make sure caching still works. Obviously, we can write that and add it to the Dancer ecosystem by publishing our modules on CPAN. However, I had hoped that this would be taken care of by Dancer(2) modules because others already ran into problems like these... My point being that I had hoped that by turning to a framework, we'd be building on the shoulders of others who ran into the same issues before we did -- I hoped to part with code. Did I miss something and is this really already part of the Dancer2 ecosystem? Would you say that my idea of the REST api implementation fits with the ideas of the Dancer(2) team with respect to "being the Dancer way" to achieve things? (Is it likely that our code becomes *part* of the ecosystem, is my question, I guess? Or will it be this code base which has its own approach to using Dancer(2)?) Thanks again for taking the time to answer these questions! Regards, Erik.
Regards Racke
5. Framework configuration Dancer2 supports running multiple "apps" on a single instance. We
<http://setup.pl> and login.pl <http://login.pl>. Even: we want these multiple apps to run on different authentication domains (i.e. when a user is logged in in /login.pl <http://login.pl>, we don't automatically want the user to be logged in into /setup.pl <http://setup.pl> too). During my experiments I found
in its configuration: it can be "tricked" to support it by installing
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
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
but there isn't for our specific use-case: our code base) 4. Route definition and dispatch (including extraction of route
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
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
want to run multiple apps: our setup.pl that this isn't something that Dancer2 supports the correct pre- and post-request hooks in the the Multi Currency branch and stabilizing the 1.7 framework which needs to support many code bases, parameters) is available in a technology agnostic that the DSL Dancer2 provides will be far less 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.
Regards, -- Bye,
Erik.
http://efficito.com <http://efficito.com/> -- Hosted accounting and ERP. Robust and Flexible. No vendor lock-in.
_______________________________________________ devel mailing list devel@lists.ledgersmb.org https://lists.ledgersmb.org/mailman/listinfo/devel
-- Ecommerce and Linux consulting + Perl and web application programming. Debian and Sympa administration. Provisioning with Ansible.
_______________________________________________ devel mailing list devel@lists.ledgersmb.org https://lists.ledgersmb.org/mailman/listinfo/devel
-- Bye, Erik. http://efficito.com -- Hosted accounting and ERP. Robust and Flexible. No vendor lock-in.
participants (3)
-
Erik Huelsmann
-
Nick Prater
-
Stefan Hornburg (Racke)