Nuxeo mailing list archives
[Ecm] cpsskins design notes (part 2) - about caching
Jean-Marc Orliaguet
jmo at ita.chalmers.se
Mon Sep 18 21:56:52 CEST 2006
Hi!
I've done further experiments with JSF/facelets/Seam, to see how these
technologies fit with the concept of a dynamic rendering engine.
So far the implementation is in phase with the design, that is, things
fit well - with no surprises, but there is a design issue that makes me
feel that I'm attempting to shove a square peg into a round hole. I'm
going to expose it now.
I also took a quick look at NXActions and I see that they same issue is
present there too, apparently unsolved.
This is really all about separating data and presentation layers and
about deciding whether to use a data-driven or a presentation-driven
design. Eventually this is also about caching data efficiently.
Background
==========
1) content or "markup" providers
--------------------------------
In the Zope2 design of CPSSkins/CPSPortlets all visual elements
(templets, portlets) return *HTML markup*.
This is a plain and simple design in which implementing a portlet means
implementing something that returns HTML, the rest is simply decoration.
This is the way the JSR-168 API generates content:
the doView(), doEdit(), doHelp() methods in javax.portlet.* produce some
markup that get written to the response with
response.getWriter().write("....")
or they can dispatch the request to another page (using
PortletRequestDispatcher) and insert it into the response, but in the
end this is still markup that gets generated by portlets.
This is also the way "viewlets" work in Zope3 (see
zope.contentprovider.interfaces.IContentProvider::render).
In these cases, the portlet's core and the portlet's presentation layer
are identical, there is no distinction between model and view.
2) the model-driven approach
----------------------------
In order to separate model and view (data and presentation of data), it
is possible to do as in CPSSchemas by saying that there is:
- a data structure with fields, a storage layer, a data model that
acts as a facade for the data structure making abstraction of the
storage and other internals, and
- a presentation layer with layouts, widgets dealing with the
presentation of data.
In the particular case of visual elements such as portlets however, this
can be simplified to having
- a transient structure holding data and
- a widget that can interpret the data,
i.e. we don't have to deal with storage or validation issues at that level.
This is by the way the concept found behind separating "presentation
beans" and JSP/JSF templates: presentation beans hold transient
presentation data obtained from the application's model and business
logic and the presentation layer is responsible for producing markup,
without having to deal with the details of how the application is built.
That is exactly the kind of logical separation that I'm thinking of
between portlet's data and widgets. It is very much "model-driven". The
presentation bean acts as a mediator between the business logic and the
presentation layer, in that case the portlet would also act as a mediator.
3) the template-driven approach
-------------------------------
Then comes JBoss Seam with the concept of "bijection", that is, of
"injection" and of "outjection" of data.
"Outjection" is very much in the spirit of what is described in point 2)
[ "bean data => template"] but "injection" is just the opposite: if a
variable is referenced in a page template, Seam will intercept and
injected data into the bean. This will occur when a user *navigates* to
a given page depending on the context (session, conversation, page, ..).
Also there is not necessarily any data in the bean prior to that.
Putting it simply: this is highly "template-driven" or "event-driven",
the data gets loaded into the presentation bean only on-demand depending
on the user's actions. And this is done in a *very contextual way* based
on the component's own scope.
Comparisons
-----------
To compare the two approaches, the model-driven approach is very generic
(ought to work in a rich-client environment), while the template-driven
approach is Seam-specific, i.e. it will work only in a web environment.
The model used in NXAction is the one described in 2), one can also read
in NXAction/src/org/nuxeo/ecm/actions/ejb/ActionManagerBean.java
public class ActionManagerBean {
...
// TODO: the seam context cannot be retrieved here
// - need to find a method to pass the seam context
public Collection<Action> getActions(String category) {
ActionContext context = new ActionContext();
// TODO setup the context
return actionService.getActions(context, category);
}
Back to cpsskins
----------------
In fact, I encountered exactly the same problem in the core component of
cpsskins, namely how to set the context in a component that knows
nothing about JSF/Seam in the first place?
I first added it "from the outside" by letting the JSF layer do a
info.setContext(facesContext);
on the data structure, but something just didn't feel right.
The idea when developing a presentation bean is to either:
- passively let Seam inject data into it or
- to actively set up a data structure which the presentation layer will
depend on (not the other way around)
But it is difficult to mix both approaches and be consistent.
So I came to the following design decisions:
* the data structures created by portlets cannot be "Seam contextual",
they have to be available directly by "mechanical computation" from the
portlet's own configuration data, for instance an RSS feed producing a
list of items as a data structure is OK
* contextual data is managed by Seam at the presentation (widget) level.
I don't see how to implement the data/presentation logical separation
otherwise.
About caching
=============
Since the management of contextual data is now delegated to Seam (or any
other context management framework) this simplifies caching issues *a
lot*. Namely it is Seam's problem to deal with session data management.
What the cpsskins rendering engine provides however is a nearly static
template which can be contextual of course but within a more controlled
scope (i.e based on the current perspective, based on the name of the
page that is being viewed, etc.).
The cpsskins widgets, instead of producing final HTML, will provide JSF
markup such as:
<h:commandLink value="#{item.name}"
action="#{workspaceActions.selectItem}" />
but not the final rendering, which is managed by JSF or Seam.
Static vs dynamic content
-------------------------
Anyone familiar with Edge-Side-Includes
(http://www.esi.org/overview.html) knows that the content of a page
template is divided into:
- static content (logotype, footer, etc.)
- dynamic content (contextual, time-based data, ...)
cpsskins currently implements such a distinction, that is, it is
possible to mark a portlet as 'static' or as 'dynamic'.
- if a portlet is 'static' it will be part of the theme with no
possibility for its markup to change over time
- if a portlet is 'dynamic' its markup will be rendered at each request
(with the possibility to add a second-level cache of course)
it is clear that static content is rendered in a much faster way than
dynamic content since there is virtually no memory or CPU overhead for
getting the data (typically one gets 300-400 req/s even on large pages).
dynamic content however is rendered in threw steps:
1) in the first step, a JSF tag is rendered instead of the final HTML
markup, for instance:
<cpsskins:portlet uid="1234" />
this gets cached in the page's template ad vitam eternam.
2) in the second step, the tag is rendered by the cpsskins rendering
engine as some HTML or JSF markup, for instance:
<h:commandLink value="#{item.name}"
action="#{workspaceActions.selectItem}" />
3) in the third step, the JSF markup is rendered as HTML:
<a href="...">item</a>
Of course if the second step always produces the same JSF markup, then
the portlet can be marked as 'static' and step 2 becomes step 1. So
'static' does not mean that the final markup never changes.
I did some benchmarks, and the 3-step rendering gives very good results
already even without optimizations (typically 100-250 req/s). My guess
is that most portlets can be marked as 'static' if JSF/Seam is used.
================
All for now :-)
/JM
This list archive provided by Nuxeo, the
leaders of open source ECM.
Check out the Nuxeo 5 open source,
standards-based ECM project.