blob: 36f13ac4a2c535a940bc782968cc138aa0d2545a [file] [log] [blame]
<!-- $Id$ -->
<!--
Copyright 2004 The Apache Software Foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<chapter id="apps">
<title>Designing Tapestry Applications</title>
<para>
When first starting to design a Tapestry application, the
designer consider some basic elements as a guide
to the overall design of the application.
</para>
<section id="apps.storage">
<title>Persistent Storage Strategy</title>
<para>
Tapestry pages store a certain amount of
client state between request cycles. Each implemention of the
&IEngine; interface provides a different strategy.
</para>
<para>
Currently, only the &SimpleEngine; class is
provided with the framework; it uses in-memory page recorders.
When the engine is serialized, the page recorders are serialized along with it.
</para>
<para>
The &IPageRecorder; interface doesn't specify anything about how
a page recorder works. This opens up many possibilities for storage of state,
including flat files, databases, stateful EJB session beans, or HTTP Cookies.
</para>
<para>
In fact, a very sophisticated application engine could mix and match,
using cookies for some pages, in-memory for others.
</para>
<para>
By default, page recorders stay active for the duration of the user session.
If a page will not be referenced again, or its persistent
state is no longer relevant or needed, the application may explicitly "forget" its state.
</para>
<para>
Remember that for load balancing and fail over reasons,
the engine will be serialized and de-serialized. Ideally, its serialized state should
be less than two kilobytes; because Java serialization is inefficient,
this does not leave much room.
</para>
<para>
The <link linkend="inspector">Tapestry Inspector</link>
can be used to monitor the size of the serialized engine in a running
application.
</para>
</section>
<section id="apps.pages">
<title>Identify Pages and Page Flow</title>
<para>
Early in the design process, the page flow of the application should
be identified. Each page should be identified and
given a specific name.
</para>
<para>
Page names are less structured than other identifiers in Tapestry.
They may contain letters, numbers, underscores, dashes and periods. Tapestry makes
absolutely no interpretation on the page names.
</para>
<para>
In many applications, certain parts of the functionality are
implemented as "wizards", several related pages that are used in
sequence as part of a business process. A common example of this is
initial user registration, or when submitting an order to an
e-commerce system.
</para>
<para>
A good page naming convention for this case is
"<replaceable>wizard name</replaceable>.<replaceable>page name</replaceable>"
(a period separates the two names). This visually identifies that several pages are related.
In addition, a Java package for the wizard
should be created to contain the Java classes, component specifications,
HTML templates and other assets related to the wizard. Having the wizard name match the package name
is also helpful.
</para>
<para>
The designer must also account for additional entry points to the application
beyond the standard home page. These may require
additional application services (see below).
</para>
</section>
<section id="apps.commmon-logic">
<title>Identify Common Logic</title>
<para>
Many applications will have common logic that appears on many pages.
For example, an e-commerce system may have a shopping cart, and have many
different places where an item can be added to the cart.
</para>
<para>
In many cases, the logic for this can be centralized in the visit object.
The visit object may implement methods for adding products to the shopping cart.
This could take the form of Java methods, component listeners.
</para>
<para>
In addition, most web applications have a concept of a 'user'.
The object representing the user should be a property of the visit object,
making it accessible to all pages and components.
</para>
<para>
Most Tapestry applications will involve some interaction with Enterprise JavaBeans.
The code to lookup home interfaces, or to gain access to ession beans, is typically
located in the visit object.
</para>
<para>
Listener code, on various pages, will cast the visit object to the appropriate
actual class and invoke methods.
</para>
<para>
The following example demonstrates this idea. Visit is a
hypothetical visit object that uses EJBs.
</para>
<informalexample>
<programlisting>
public void exampleListener(&IRequestCycle; cycle)
{
Visit visit; <co id="apps.common-logic.ex.visit"/>
<replaceable>ISomeHomeInterface</replaceable> home;
visit = (Visit)getVisit();
home = visit.<replaceable>getSomeHomeInterface</replaceable>();
try
{
// etc.
}
catch (RemoteException ex)
{
throw new ApplicationRuntimeException(ex);
}
}
</programlisting>
</informalexample>
<para>
<calloutlist>
<callout arearefs="apps.common-logic.ex.visit">
<para>
Each application can freely define the type of the visit object,
and its is common to call the class "Visit". Another option is
to create a subclass for the engine and store home interfaces
there.
</para>
</callout>
</calloutlist>
</para>
</section>
<section id="apps.services">
<title>Identify Engine Services</title>
<para>
Tapestry applications will need to define new engine services when a page
must be referenced from outside the Tapestry application
</para>
<para>
This is best explained by example. It is
reasonable in an e-commerce system that there is a particular page that shows
product information for a particular product. This information includes
description, price, availability, user
reviews, etc. A user may want to bookmark that
page and return to it on a later session.
</para>
<para>
Tapestry doesn't normally allow this; the page may be bookmarked, but
when the bookmark is triggered, the page may not render correctly, because it
will not know which product to display. The URLs normally generated in a
Tapestry application are very context sensitive; they are only meaningful in
terms of the user's navigation throughout the application, starting with the Home page.
When bookmarked, that context is lost.
</para>
<para>
By defining a new engine service, the necessary context can be encoded directly
into the URL, in a way similar to how the direct action works. This is
partially a step backwards towards typical servlet or JSP development, but
even here Tapestry offers superior services. In the e-commerce example,
the service URL could encode some form of product identifier.
</para>
<para>
An example of this is in the Virtual Library application. In
order to make certain pages bookmarkable, a new service named
"external" was created.
</para>
<para>
The external service includes the name of a page and the primary key
of an object that page displays (this was simplified by the
design of the Vlib entity beans, which always use an &Integer;
as the primary key).
</para>
<para>
The external service works much the same was as the page service, except
that it invokes an additional method on the page, <function>setup()</function>,
which is passed the primary key extracted from the URL.
</para>
<para>
The end result is that when a user arrives at such a page,
the URL used identifies the page and the primary key. Bookmarking the page stores the URL so
that when the bookmark is later opened, the correct data
is read and displayed.
</para>
</section>
<section id="apps.common-components">
<title>Identify Common Components</title>
<para>
Even before detailed design of an application, certain portions of pages will be
common to most, if not all, pages. The canonical example is
a "navigation bar", a collection of links and buttons used to navigate to
specific pages within the application. An e-commerce site may
have a shopping cart related component that can appear in many places.
</para>
<para>
In many cases, common components may need to be parameterized: the navigation bar may
need a parameter to specify what pages are to appear; the shopping cart component
will require a shopping cart object (the component is the view
and controller, the shopping cart object is the model).
</para>
<para>
Other examples of common components are viewers and editors of
common data types.
</para>
<para>
In the Virtual Library, components that make use of the external service were
created. The components, <classname>BookLink</classname> and <classname>PersonLink</classname>, took as
parameters the corresponding objects (<classname>Book</classname> or <classname>Person</classname>)
and created links to the pages
that displayed the details of that <classname>Book</classname> or <classname>Person</classname>.
</para>
</section>
</chapter>