Ok. What is the problem with the render_to_psgi() functions when you throw
away the success/fail indicator and convert that to a 'die()' when the value isn't equal to HTTP_OK ? I mean, that amounts to the same thing as the pair you're proposing? The body is one of two things: an array of strings or a filehandle. That should be easy to convert to a string (as you want it?).
That's one too many options for the body in my view. It means unneeded complexity given that different formats will probably return differently here and you always have to handle both.
Yes. That's exactly the point: you *have* to handle both. In any reasonable context, you can't know what the size of the returned document is and there should not be any assumptions as to whether the document can or needs to be loaded into memory in its entirety.
I would like to see, ideally, one response format only. A file handle would be good if we standardised on that.
But from a performance perspective, it's not always desirable to wrap content in a wrapper object. And from a DDoS perspective, it's not always desirable to load the entire response in memory. To that extent I think that the PSGI interface has been designed pretty well. LaTeX->PDF documents are already on disk and can be returned using a file handle. HTML documents are (usually) already in-memory and can be returned using scalar references. Here's a case in point: Suppose I want a cron job which emails a
spreadsheet of overdue invoices (ageing detail report). I think this should be a fairly easy thing to do even with the global state requirements, but on 1.4 it goes something like "fork our reporting engine" and the problem still is not fully fixed in master (worse, it is not *obvious* that the problem is not fully fixed in master because of assumptions in the templating engine).
I'm afraid I'm missing the point. In my mind you'd need to instantiate a LedgerSMB::Report::Aging, call its "render_to_psgi()" method and take the body out of the returned triple. Then you do with that body whatever you want.
I'm not sure I see what the problem is that isn't completely fixed in master.
Quick: does the csv and OOO adaptors return bodies with the same interface? Or do I have to find out what kind of reference the body is and handle it?
You always have to do the same thing, which means: Use Plack::Util to grab the returned body.
And why does the template library have special knowledge of HTTP status?
Because we never settled on a good error handling model. Some parts of the code are returning undef, others are dying in combination with Try::Tiny (try/catch). Until we decide about a fitting error handling model, I don't see other options than to simply be pragmatic and apply the examples from the library we're using (e.g. Plack/PSGI in this case).
(Note that I'm avoiding to say that we have resolved the problem that none
of our modules behave as "good libraries" and make lots of assumptions about the context in which they're being called, like accessing and/or assuming global state having been set up.)
Agreed. One of the point of trying to formalise things that can be broken off is to make good libraries and a smaller footprint. But this is about two things. The first is general interface and having an interface which avoids assuming that every component is being accessed from the whole framework.
Yes. We completely agree there.
WAIT! While going over the response above, I'm suddenly realising your use-case is with the aging report!
Then it's my time now to say that you're confusing one problem for another. Our LedgerSMB::Template is conflating two completely unrelated (although both "template" related) problems: LedgerSMB::Template generates our UI *and* LedgerSMB::Template generates our templatized documents.
Yep. And the UI portion will probably eventually go away.
If you mean "go away from LedgerSMB::Template (into LedgerSMB::UI)", then I can follow. However, I'm not so optimistic about LedgerSMB::UI going away. I'm thinking it might switch template processors, for speed purposes, but I'm not (unfortunately) seeing it be replaced by Dojo completely.
While doing the latter, it even mistakenly sends them as "UI responses"
(HTTP responses).
I've long wanted to separate the "UI templates processor" from the "document templates processor". Having that separation resolves 99% of the cases where people may want to run reports from outside the context of a web request.
Exactly what I am trying to get to.
Ok.
I'm not convinced that separating "render_to_psgi()" out from the "UI
templates processor" actually brings us anything: Dancer's "template()" function is very tightly nit to the response being sent too.
Right but Dancer's template engine assumes you are going to use it with Dancer, to generate web pages with HTML and jquery. I guess I am questioning that assumption for an accounting package.
Ah. But you're looking at it wrong. We're using LedgerSMB::Template as part of the "LedgerSMB web glue layer". It has nothing to do with the accounting package LedgerSMB, but much more with LedgerSMB's infrastructure. We've been writing all our components into the single application because these frameworks didn't exist yet. And while we're moving to Plack quickly, we're not completely rewriting the application to use Dancer (yet). So, we still need our custom glue layer for a while.
I think these two are closely connected and global state is a part of
the second.
The case that again immediately occurred to me here was the fact that LedgerSMB::Report->render sends the result in CGI format, despite the fact that this is a module introduced in 1.4.
We didn't "do" PSGI-native-responses until 1.6, so, both 1.4 and 1.5 return CGI responses which are being converted to PSGI responses on the request-dispatch boundary.
And even in master, you have a choice of PSGI or CGI only (because for historical reasons we output to cgi by default). Yes, we can fix it in the reporting engine by adding a line of code but I guess I see this as a single responsibility problem because the templating engine takes on the responsibility of output which I think is a problem.
The problem is that we have a single templating engine to generate our UI output as well as our "documents" (invoices, reports,...) output. (And because they were conflated, we stored the report templates in the UI/ tree, instead of in the templates/ tree (!), with the huge downside that they're not translatable or per-locale modifiable.)
Right. So suggesting we a shim for old code and a clean separation of concerns.
I'd like to avoid using shims: they obfuscate the execution flow (generally) and tend to stick around way longer than planned and desirable. Can this same effect be achieved by *not* using a shim and simply coding "the right thing"?
(But if it doesn't take on the responsibility to output then I don't think
it should take on PSGI responsibility either.)
There is a related question here which is where should the HTTP status messages come from but I think that's an additional discussion. I think they should only come from the workflow scripts or from PSGI.pm but open to other ideas.
Now we're getting back to the beginning of the discussion: if the UI templating engine doesn't take care of generating the actual output, then what role *does* it have? I mean, expanding the template is a function already performed by Template Toolkit...
I guess that's the question, isn't it?
Here's what I would like to see (and I think this goes well with the efforts to move to a more service-oriented front-end): I would like to see the template infrastructure be a report and document generator which abstracts generation of documents in a variety of formats for the programmer. That's one responsibility. It is a narrow one. We can build something that then works for the future by ignoring what happens after we generate the document.
Ok. I like that. As long as the definition of "document" here excludes the HTML being generated as part of the HTTP response generation. Because that's an entirely different league of "documents" than the documents I think the engine should focus on: the user-visible, possibly translatable or adjustable envelopes, invoices, and loads of reports.
Put another way, what I see the module doing is providing an abstraction around "I want a report" and "btw, I want it in pdf format" or "I want it as a csv." Or "I want an invoice" and "Please make it a PDF."
Ok. If you want it maximally flexible for the future, I think we want "I want an invoice. Please use <this> template. Please make it a PDF" (because then we can maintain documents which are PDF output from LaTeX while others can be PDF from HTML).
Now to do this it has to do two things:
1. Maintain knowledge of available formats on the system (and possibly be able to do so on a per-template basis, something we are currently missing), and 2. It has to provide a stable interface for format providers to use to communicate with it and to the program as a whole.
This means that what we have now for Template.pm in my proposal is basically a point of dependency injection, which maintains the responsibility of being aware of what dependencies have used it (PDF, HTML, etc). The Template::LaTeX, Template::HTML, etc. then take on the responsibility of plugging in to allow document generation in a singularly consistent API.
Ok. But I found Template Toolkit to be extremely flexible (we implemented it to read from the database instead of from a filesystem, completely plugin-based!). So, I'm still wondering if this can't be achieved with Template Toolkit and a few wrappers and plugins.
That sounds small and narrow and it is. But think about what that enables as well:
1. Dynamic format detection (What formats do I know about? PDF, HTML, PS, OOO, and Gnumeric*) 2. Dynamic template detection (What formats can give me an invoice with my current template list?)
* hypothetical new format we just installed
Note we have to provide a shim for old code.
Why? I mean, old code (as in code in old/) typically invokes the template processor to generate a true document (the HTML responses are "composed" inline). The difficulty might be existing code in lib/. Although there you could probably search for 'render_to_psgi' and try to establish if there's a document or a UI being generated in that specific invocation. (All 'render()' invocations probably are real documents being generated.)
But I think the stabilisation and generalisation here would be helpful.
More tangibly, it would allow someone to write an HTML -> PDF template engine third party, and just have it plugged in if that was still desired by anyone, or for someone who wants to be able to generate OOO doc invoices to be able to do so without demanding that everyone else support the functionality.
At any rate that's what I had in mind.
Thanks. That's much more detailed and as such better understood (by me) as to where you're headed. So, to reiterate: I think splitting UI and "document" template engine wrappers into two categories sounds like a major, good step forward. I'd like us to try really hard to stay away from shims as something that's more than just a proof-of-concept; in the sense that it can be a way of hooking something in, but I'd say that the refactoring is only done when the shim is gone. Regards, -- Bye, Erik. http://efficito.com -- Hosted accounting and ERP. Robust and Flexible. No vendor lock-in.