| <!-- $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> |