Designing Tapestry Applications</> <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. </> <section id="apps.storage"> <title>Persistent Storage Strategy</> <para> Tapestry pages store a certain amount of client state between request cycles. Each implemention of the <classname>IEngine</> interface provides a different strategy. </> <para> Currently, only the <classname>com.primix.tapestry.engine.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> The <classname>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> In fact, a very sophisticated application engine could mix and match, using cookies for some pages, in-memory for others. </> <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> 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> The Tapestry Inspector can be used to monitor the size of the serialized engine in a running application. </> </section> <section id="apps.pages"> <title>Identify Pages and Page Flow</> <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> 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> 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> A good page naming convention for this case is "<replaceable>wizard name</>.<replaceable>page name</>" (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> 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). </> </section> <section id="apps.commmon-logic"> <title>Identify Common Logic</> <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. 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> 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> 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> Listener code, on various pages, will cast the visit object to the appropriate actual class and invoke methods. </> <para> The following example demonstrates this idea. Visit is a hypothetical visit object that uses EJBs. </> &start-listing;public IActionListener getListener() { return new IActionListener() { public void actionTriggered(IComponent component, IRequestCycle cycle) { Visit visit; <replaceable>ISomeHomeInterface</> home; visit = (Visit)getVisit(); home = visit.<replaceable>getSomeHomeInterface</>(); try { // etc. } catch (RemoteException e) { throw new ApplicationRuntimeException(e); } } }; }&end-listing; </section> <section id="apps.services"> <title>Identify Engine Services</> <para> Tapestry applications will need to define new engine services when a page must be referenced from outside the Tapestry application </> <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> 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> The built-in action and direct services reject any URL if there isn't an existing <classname>HttpSession</>. They redirect to a StaleSession page, that warns the use of what has occurred and allows them to start the application fresh (a standard page for this is provided by Tapestry, but can be overridden by an application that wants to provide different content or look-and-feel). <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> An example of this is in the Primix Virtual Library application. In order to make certain pages bookmarkable, a new service named "external" was created. This involved subclassing <classname>com.primix.tapestry.engine.SimpleEngine</> to provide the new service. </> <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<classname>Integer</> as the primary key). </> <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()</>, which is passed the primary key extracted from the URL. </> <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. </> </section> <section id="apps.common-components"> <TITLE>Identify Common Components</> <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> 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> Other examples of common components are viewers and editors of common data types. </> <para> In the Virtual Library, components that make use of the external service were created. The components, <classname>BookLink</> and <classname>PersonLink</> , took as parameters the corresponding objects (<classname>Book</> or <classname>Person</> ) and created links to the pages that displayed the details of that <classname>Book</> or <classname>Person</> . </section> </chapter>