Kiss My App

Friday, March 04, 2005

Stateless Components

Since commenting on Tapestry vs. JSF I've realizing that both aren't exactly ideal, IMHO. Since then, I've come to the conclusion that components should be stateless and work within a simpler momento system.

The 80/20 Case


Components wear two hats-- displaying and updating. Most components, the 80% case, purely serve the view while only 20% actually have interactive data. The situation begs the question, why add so much overhead to the framework for managing state for 80% of the components that only exist to display data?

How Components can Be Stateless


First think about a JSP page that executes twice-- one for displaying, then posts back to itself for updating. This comes with the caveats that every component used in displaying must have the opportunity to take part in updating on the post back. JSP creates problems since there's no garauntee that every component used in displaying could participate in updating. This is causing problems for JSF and while the EG has come up with a suitable solution, there's still the issue of the quantity of state that now must be maintained. Tapestry gets around this by pooling objects to save on memory use in managing state.

What I'm suggesting is that we guarantee a consistent component tree for all phases of the lifecycle. This removes the heavy overhead of having a component framework manage component trees for you (bandwidth issues with overall latency and memory issues). Instead, the 'static' component tree operates on a momento object, just like frameworks operate on the Request/Session/Application scope. This momento object is stored much like JSF's ViewState, but only needs to preserve tidbits of a component's state.

A new Page Technology


We first define a page as being a stateless component, which means the page operates much like a Servlet (something Java developers are very familiar with). Since components are stateless and the component tree is generated once and used by everyone, the attributes cannot be dynamic-- or can they? Instead of following JSP and evaluating EL for attributes on a tag, instead you allow a component (tag) take in a ValueExpression (new EL-API). Then, at each invocation, the ValueExpression can be evaluated to produce the same dynamic behavior that JSP offers within a stateless environment.

But there's a catch. Since components are stateless, each phase: rendering and updating, must execute and work with state in a single method invocation. So a component has a 'onRender' event where the page context is passed, the component captures dynamic state and then calls 'onRender' on its children, scoping state for a single invocation by a Thread-- same as a Servlet's 'service' method. Apologies if I sound confusing at this point.

Logical Components


Remember I said that every component used in rendering must have an opportunity to participate in the update phase? So if I have a document that uses <c:if> behavior in rendering, then how can I guarantee that the update phase will offer the same result? I hinted in the last paragraph that the page context is passed to onRender. This allows <c:if> to store its state in the page context that will be used in updating also, much like JSF's ViewState, but we are cutting all the fat out.

protected boolean evaluateTest(PageState state) {
// get an Id from the page, we do this to accomodate
// components being executed repeated times <c:forEach>
String clientId = state.getClientId(this.getId());

// this could be executed in a post back
Boolean b = (Boolean) state.getState(clientId);
if (b == null) {
// this is a new 'request' cycle, so generate b
Boolean b = (Boolean) this.test.getValue(Boolean.class,
state.getELContext());

// store it for the life of this user's request
state.setState(clientId, b);
}
return b.booleanValue();
}

public void onRender(PageState state) {
if (evaluateTest(state)) {
this.renderChildren(state);
}
}

public void onUpdate(PageState state) {
if (evaluateTest(state)) {
this.renderChildren(state);
}
}

In the above example, for the lifecycle of a PageState (render, update, and possibly render again, repeat--), the single Boolean value can be captured and stored, guaranteeing a consistent execution on every request.

Really, the 'evaluateTest' method could be abstracted into a parent component such that all ValueExpressions can have their state maintained for you by delegating the logic to a single method.

Conclusion


I'm still working out all the details, but I even have most of a 'repeater' example running that works with <c:forEach> without the need for a special data model to back any child form components.