| ---- |
| Tapestry for Struts Programmers |
| ---- |
| |
| Introduction |
| |
| There's no getting around it ... Struts is the 800lb gorilla of Java web application frameworks. |
| It was on the scene early, it got wide coverage and wide adoptions, and vast numbers of |
| applications have been written in it. |
| |
| Struts is an <action based> framework, and exists as a kind of thin wrapper on top of the |
| base Servlet API. This is good as far as it goes, but Tapestry has always existed |
| as an example of why that view of the world is limited. |
| |
| Tapestry, especially Tapestry 5, represents a somewhat radical approach compared to |
| Struts. You will find it to be quite compelling ... but it also requires adjusting your |
| mindset to a completely different way of looking at building web applications. |
| |
| Struts: Actions and Views |
| |
| Tapestry and Struts approach the division of concerns represented by the Model-View-Controller |
| design pattern from very different angles. |
| |
| Struts maps incoming URLs to <actions>; these actions are objects that extends from the |
| Action base class. |
| |
| What does an action do? It reads query parameters in the request either directly, |
| or via an associated ActionForm object. It probably talks to your backend to read information |
| from a database, or write information out to it. It probably stores some information |
| in the request, information that will be needed by the <view> when it renders. |
| Finally, it returns the name of the view. |
| |
| The framework picks it up from there; it uses that view name to select |
| a template, usually a JavaServer page, |
| and gets the template to render a response to the user. |
| |
| How does Struts find all this? Lots of XML configuration; endless details for each |
| and every "page" to define the controller class, the map view names to JSPs, to link |
| views and actions to ActionForm classes. |
| |
| The Problem of Statelessness |
| |
| Where does Struts (and similar frameworks) get things wrong? The first issue is with |
| the management of <state>. |
| |
| Remember "<Beginning Object Oriented Programming 101>"? Objects are generally defined |
| as a software construct that encapsulates behavior <and state>. |
| |
| Struts actions do not have internal state. They can't -- they're singletons, one instance |
| shared by however many threads are operating within the servlet container. Any internal state, |
| which is to say, any <instance variables>, would be immediately overwritten by some |
| other thread servicing a request for some other user. |
| |
| Struts approaches this problem by externalizing state: into the HttpServletRequest (for |
| state needed just by the view), or into |
| the HttpSession (for state that must persist from one request to the next). |
| |
| Basically, what we have here is a crippled form of object oriented programming: |
| objects that have a single behavior, and have no internal state. |
| |
| As we'll see, Tapestry addresses both of these issues: in Tapestry, components |
| can have internal state, and they may have not just one, but many different behaviors. |
| |
| Views: An Artifical Barrier |
| |
| Action frameworks create a separation between the behavior logic, the code inside |
| a Struts Action, and the view logic, which it typically a JavaServer Page. |
| |
| The <artificial barrier> is the lack of linkage between the template and the controller |
| (the Struts Action). The only line of communication is the data in the HttpServletRequest |
| and HttpSession. |
| |
| There's a purpose to this design: it's all about the choice for the view. Because the |
| action may select one of several views, it's necessary to loosely couple the action |
| and the view. The HttpServletRequest, and the named attributes stored in the request, |
| is the form of this loose coupling. |
| |
| This puts an onus on the action to stuff into the HttpServletRequest any and all data |
| that might be needed by the view as it renders a response. After all, there's no other |
| alternative, is there? |
| |
| Additionally, there's the issue of getting the action and the view, or views, to agree |
| on the name and type of every bit of state stored inside the HttpServletRequest or |
| HttpSession. Small changes to the controller, such as storing a different piece of data, |
| or using a different name, can have a ripple effect inside the view. |
| |
| This makes sense, doesn't it? After all, the result of an action (such as clicking a link |
| or submitting a form) may be some kind of success, or some kind of error. Or perhaps |
| you decide on the flavor of output based on the kind of client: HTML for the desktop or WML |
| for a phone. |
| |
| Perhaps in theory, but certainly not <in practice>. Errors are rarely presented as a whole |
| new view; they are usually presented as additional content within the same view. As to the |
| myth of a single application that vends out different markups for different clients ... |
| that is just a myth. It's not just about output, but about every aspect of application development. |
| It's more than just what content shows up where ... it's about the very functionality offered |
| and the use cases supported, which are very different from one device to another. Pull back the covers |
| on any single application that supports such diverse clients and you'll find multiple applications, one |
| per device type, squeezed together as a not-so-coherent package. |
| |
| Components: Its Not Just About Output |
| |
| What happens when you have common <fixtures> to your application? By fixtures, |
| we mean, bits of the view that are used in many places throughout the application. |
| This includes large chunks of functionality, such as a menu system used on every page, |
| down to tiny bits of functionality, like some kind of "widget" to format a date for output. |
| |
| JSPs provide two approaches for this kind of behavior: you can use JSP includes, to reuse |
| JSPs snippets across many larger JSPs. Alternately, you can define and use JSP tags, which provide |
| a way to generate output using code, and provides mechanism for passing information from the |
| HttpServletRequest into the JSP tag implementation. |
| Alas, in the real world, the vast majority of actions do have just a <single> view, named |
| "success" by convention. After all, even when an error occurs, you don't want to lose |
| all context ... if a user enters incorrect information into a form, then you want |
| to redisplay the form with the error messages. The view has to <adapt> to the state |
| determined by the request. |
| |
| But what happens when the "fixture" has its own behavior? It's own state? |
| |
| For example, maybe a fixture is a login form |
| that's displayed on every page until the user logs in. The form may have a URL that |
| points to a login Action ... but how do you return to the same view after logging in? |
| A similar case may be a component that displays tabular data and supports paging. Such |
| a component will have links for navigation and sorting. How do the actions for those links |
| return the user to the same view after changing the set of displayed items? |
| What if there's more than one such component in a single view? |
| |
| You end up writing more and more configuration and boilerplate code. |
| You must define more and more JSP attributes |
| or other tricks in order to tell the appropriate action how to get the correct view after |
| performing the actions. |
| |
| You've run into the limitation of not having true web components on your side. |
| |
| With Tapestry, individual components can have their own interactions, blended seamlessly into the page, even |
| when the same component is used across multiple pages. Tapestry is able to keep everything organized |
| because it has its own view of the application, the component object model. |
| |
| Tapestry: The Full Cycle Solution |
| |
| What Tapestry offers is <structure>. A well defined, fixed structure of pages and components within |
| pages. This structure is used in every aspect of Tapestry, including state management, output rendering, |
| and request dispatching. |
| |
| You don't have to worry about URLs. Incoming requests for Tapestry encode the |
| name of the page and the id of the component within the page, along with other information, |
| into the URL for you. Your code never has to pick apart a URL, you create |
| {{{guide/event.html}event handler methods}} to know when the user has clicked a link |
| or submitted a form, or done something more interesting using Ajax. Tapestry has the structure it needs |
| to build all necessary information into the URL, and unpack that information in a later request. |
| |
| Tapestry is truly object oriented: you don't have to be concerned with singletons and multithreading; your |
| pages and components have instance variables like any other plain Java object. Tapestry lets your write |
| your application using true object oriented techniques. |
| |
| In terms of state management: Tapestry takes care of |
| {{{guide/persist.html}persisting field values into the session}}. You don't have to figure out mnemonic, |
| unique names for session attributes, or write code to pull data out of the session or push it back in. |
| Tapestry is able to do this for you. |
| |
| Further, in Tapestry pages and components are identical. They are <consistent>. Pages have templates, so |
| do components. Pages have transient and persistent fields. So do components. Pages have event handler methods, |
| so do components. |
| |
| Tapestry doesn't have the idea of multiple views for a single page. However, processing within one page can easily "hand off" to |
| another <page>, in Java code, to provide the response to an action by the user. |
| |
| Making the Transition |
| |
| Don't expect there to be a magic tool to convert an existing application to Tapestry 5. The worlds |
| between Struts and other action oriented frameworks, and Tapestry, are just too far. |
| |
| With Tapestry, you can start to think of your application in the same terms as your users ... as a collection |
| of pages that are connected together. |
| |
| You don't create URLs and map them to singleton classes; you put a <component> in your page, and |
| add an {{{guide/event.html}event handling method}} to your class to be invoked when that component is triggered. Many components? |
| No problem, just add more event handler methods. |
| |
| Leave Behind the Pain |
| |
| Tired of constantly rebuilding and redeploying your application every time you make a change? Tapestry features |
| {{{guide/reload.html}live class reloading}}. Every time you make a change to your code or your templates, Tapestry picks up the |
| changes immediately ... no waiting! Just think about how fast and easy building an application is without the expected |
| constraints; scripting language speed with all the power of Java. |
| |
| |
| |
| |
| |