Rick Hightower's recent article, "JSF for nonbelievers: Clearing the FUD about JSF" has
been prompting some discussion on TheServerSide. Now, one of
the good things about JSF is the way it validates (in some people's eyes) the component based approach.
It's natural for me to be politely antagonistic towards JSF. JSF has an event model
and a component model and I find it bothersome for them to claim that JSF has
the event model and the component model. Their choices are just their choices.
The one thing to keep in mind during any discussion of these technologies is that, from a high enough
level, they are all identical. HTTP requests flow in, HTML (or other) responses flow out.
This is the same for CGI, Perl, ASP.NET, etc. The differences are not in what's
possible, but in how hard it is to create and how easy it is to maintain.
While I'm envious about certain aspects of JSF (particularly enforced acceptance from above,
lack of enforced inheritance, and having design-time tool support built in),
my basic stance, unchanged from reading this article, is that real-world
Tapestry applications
are faster to build, easier to maintain and extend, and
more robust in deployment.
JSF and JSP technology
I find this aspect of JSF quite interesting; the choice to "support" JSP creates some strange
behaviors that would be unacceptable if it wasn't coming out of Sun and the JCP. In Tapestry,
when any aspect of a page or component changes (and you've disabled caching for development
purposes), then templates and the component tree stay synchronized ... because they are the same
thing.
Figure 1. Example application from an MVC point of view
The Tapestry equivalent of this is, I think, a bit simpler. There's a lot less to map
because the Tapestry HTML template is tied to the component tree. Tapestry component
and page classes are the equivalent of the JSF backing bean (though it is quite practical to have
both properties and listener methods in a different object entirely).
faces-config.xml
One of the things that bothers me about JSF is the fact that managed beans are entirely
global. Tapestry allows each page or component to manage its own set of beans, and has additional
lifecycles (the equivalent of bean scopes).
For me, the navigation rules are problematic. Because of how the view (templates)
and component model are so loosely tied together, it becomes necessary to add
another level of abstraction, the outcome/view-id mapping. If you corner a JSF developer and
challenge them about the need for this, the response will likely be something about supporting
different views (i.e., WML, Flash, etc.) from the same set of controls.
And that's a problem for me. I've repeatedly asked my audiences at various events who has
done this, or needed to do this ... support multiple view types with a single application.
I've yet to find anyone who needs to do this or finds it practical.
This was the promise of XML/HTTP pipelines before JSF. The problem is, this is a non-starter
of an issue ... there's no such thing as a single application that support multiple views.
What there are is multiple similar applications that share common back end processing and data.
When you try and have one application shoe horn into multiple view technologies, you are
asking for a disaster ... I call it "coding inside a case statement". You'll be adding, removing,
moving and morphing so much stuff that you end up with something that is brittle in all views.
My response when this kind of feature is asked for in Tapestry is to challenge the
questioner about what the different forms of the application will look like ... and to point out
that they are different applications that should share a common back end. In Tapestry terms, they
may even share page classes and components ... but the templates should be unique to
each view. The ultimate goal is to keep the templates clean because any other
approach is incompatible with enterprise application development realities.
Good for demos, bad for real life.
By contrast, Tapestry pages concentrate on just on type of markup at a time, typically
HTML (but just as easily XHTML, XML or WML). This allows the kind of fine-grained control
that demanding page designers expect.
Case in point is Tapestry's support for informal parameters. Tapestry components
may optionally support additional, arbitrary parameters beyond the
explicit, named (and typed) set of parameters. The majority do. The parameters (which
may be literal values, evaluated expressions, or localized message -- just like
formal parameters) are passed through as additional attributes on the element. This allows
you to specify any and all HTML attributes; particularly useful for dealing with CSS styles
or JavaScript event handlers. Having a tight binding between the template engine and
the component model makes this easy ... using JSPs (where each JSP tag attribute must be
formally defined) would make this impossible.
A final note; I'm dissapointed that the name of the calculator controller bean, CalcBean,
is a "simple" name, not a qualified name. From the later examples, it appears that
such simple names are mandated by the expression language. On large projects, this
will simply encourage naming conflicts. My experience, as far back as 1997, is that naming conflicts
can wreak havoc on large team projects ... and Tapestry is specifically designed so
that naming conflicts simply don't occur.
Gluing the model and the view
Tapestry allows you to have as much or as little flexibility as you want in terms
of where properties and operations are located. My preference is to co-locate properties
and operations since that is the very definition of object-oriented programming.
In Rick's example, he demonstrates how the pure business logic can be externalized, by
having a Calculator class that is separate from the CalculatorController that is referenced
inside JSF.
Certainly the same thing is possible in Tapestry. The equalivalent
Tapestry page class would be:
public abstract class CalculatorPage extends BasePage
{
public abstract int getFirstNumber();
public abstract int getSecondNumber();
private Calculator _calculator = new Calculator();
// Listener method for adding.
public void add(IRequestCycle cycle)
{
int result = _calculator.add(getFirstNumber(), getSecondNumber());
showResult(result);
}
public void multiply(IRequestCycle cycle)
{
int result = _calculator.multiply(getFirstNumber(), getSecondNumber());
showResult(result);
}
private void showResult(int result)
{
IRequestCycle cycle = getRequestCycle();
ShowResult page = (ShowResult) cycle.getPage("ShowResult");
page.setFirstNumber(getFirstNumber());
page.setSecondNumber(getSecondNumber());
page.setResult(result);
cycle.activate(page);
}
}
This demonstrates some advantages of JSF
and some advantages of Tapestry. JSF doesn't require you to extend
from a base class, which is a good thing (and a huge, backwards incompatible
change for Tapestry, which is why it hasn't been implemented yet).
The abstract class, with abstract accessor, causes a bit of confusion; it is because
Tapestry injects code into your class by subclassing it and filling in the
abstract methods. In this way, it can efficiently manage the properties of your
page ... storing persistent properties in the HttpSession as they change, and properly
resetting the values for transient and persistent properties at the end of each request.
This reflects the Efficiency principle of Tapestry ... the expensive to construct page objects are pooled
between requests and shared by different sessions from one request to then next; the enhanced
subclass fulfills the contract needed by Tapestry to safely share the page objects in
this way.
The method invocation is the same; Tapestry 3.0 requires that such listener methods
take a single IRequestCycle parameter (Tapestry 3.1 will likely improve on this).
The difference is how that method is reference; in Tapestry, the Submit component can reference
the method as:
<input jwcid="@Submit" listener="ognl:listeners.add" value="Add"/>
In Tapestry syntax; this means "an anonymous instance of the Submit component, with its
listener parameter bound to the add method of the page class".
An advantage for Tapestry, I think, is the way the two pages (the firsts containing
the form, the second displaying the result) communicate ... in proper, type-safe Java code.
The first page obtains an instance of the "ShowResult", casts it down from IPage to
its actual type, and invokes methods on it to inform it of what it needs to operate.
This is a clean interface between the two pages ... the first page
concerned with collecting the two values and calculating a result, the
second with displaying the two values and the result. The are many variations on
this "Tapestry bucket brigade" that will appear more or less efficient. For example, we could have a data object containing the first and second numbers and the result, and pass that single object between the pages.
Again, this is more of an object-oriented approach. The pages of the application are
objects, at least for Tapestry. The proper way for them to communicate is
via methods and properties ... rather than the engineered coincidence that they both
reference the same, arbitrarily named bean stored as an HttpServletRequest attribute. JSF is
much more beholden to the Servlet APIs than Tapestry ... which does much more to hide the
APIs and the mechanisms they represent.
A final note on this subject; in Rick's example, the CalculatorController was given session scope; a somewhat odd choice. I suspect the reasoning for this will become evident as the examples expand in later chapters. In any case, the Tapestry example will be request scope and the application will itself be stateless (no HttpSession).
JSP vs. HTML template
In this simple example, JSF has a slight advantage, in that JSF input fields include
validation by default. Even so, the JSPs here would not preview correctly inside
any HTML editor ... it would require a JSF-aware tool to render a preview properly. By contrast,
Tapestry HTML templates are ordinary HTML elements and attributes (with an occasional extra, non-HTML
attribute thrown in) and will preview properly in any WYSIWYG HTML editor.
In Tapestry, form input validation is an add-on, requiring
a different component, TextField. However, Tapestry's form input validation is quite
powerful, with tremendous control over look and feel, error message formatting
and reporting, and support for complex forms containing loops and conditionals.
Conclusions
Rick has demonstrated that your can assemble simple JSF applications without tool support.
JSF does have an improved model over typical Struts development.
I look forward to more articles
in this series, as I think it will prove an excellent way of differentiating Tapestry 3.0 from JSF.
Certainly, I've seen nothing in any publication about JSF that would make me consider "closing up shop".
At a high level, yes, the seem quite similar ... but there are basic assumptions and
pervasive practices in both JSF and Tapestry that result in worlds of differences
when you sit down to build a real application.
I expect to take a few minutes to put together Tapestry versions of Rick's examples. Monitor this
blog for the details ... and remember that Tapestry discussions are best
held on tapestry-user@jakarta.apache.org.