23 May 2008
Read Developing Flex RIAs with Cairngorm microarchitecture – Part 1: Introducing Cairngorm before reading this part.
Beginning
Throughout the series you will find references to code taken from an e-commerce application named Cairngorm Store. You may use this sample application to gain a better understanding of Cairngorm, but please consider it only as a guide. Adobe is not responsible for maintaining the Cairngorm Store application.
In Part 1 of this series, we introduced Cairngorm, a lightweight technical architecture that simplifies much of the complexity that recurs in the development of large-scale rich Internet applications, called enterprise RIAs.
In Part 2, we describe one of the challenges you’ll face in developing enterprise RIAs—specifically, how to keep state on the client. You'll learn about two fundamental patterns in the Cairngorm architecture: the Value Object pattern and the Model Locator pattern. You will also gain a better understanding of the clarity you can achieve within your own Flex application development by using these patterns.
Newcomers to Flex are encouraged to explore the Flex samples area of the Adobe Developer Connection.
Samples such as the Flex 3 Dashboard, the Flex 3 Component Explorer and even the Flex Store (Flex 2) offer an excellent opportunity to study the features of the Flex application framework. They demonstrate how to use layout containers, navigator containers, controls, effects, data binding, the Drag and Drop manager, form validation, and the History manager. The Flex Store, in particular, clearly demonstrates the component-development model of Flex, showing how to create loosely coupled components that integrate through an event-driven architecture. Exploring the Flex Store application is an excellent way to become familiar with declarative layout using MXML and business logic development using ActionScript.
The Cairngorm Store (see Figure 1) is a complete rebuild of the Flex Store that transformed it into a scalable, maintainable enterprise rich Internet application through Cairngorm.
As an RIA, The Cairngorm Store is just complex enough to benefit from a Cairngorm architecture. Indeed in Part 3, we demonstrate this tipping point by showing how, within the rearchitected Cairngorm application, you can add new functionality easily, more predictably, and with significantly less development risk than if you were to attempt to add functionality to a non-Cairngorm code base.
We will use this application as a guide to highlight the key features of Cairngorm throughout the next four parts of this series.
Instead of an academic discussion of the design patterns we selected for Cairngorm, we will explain the common high-level challenges that often occur when developing rich Internet applications and how to address these challenges with a Cairngorm development process. While describing the development process, we will also explain the various constituent design patterns used in the Cairngorm framework.
A good technical architect views application development first as a solution to a business problem, second as a system that realizes this solution, third as a technical architecture for the custom software in the system, and finally as the detailed class-level implementation of that technical architecture.
This approach, like the Cairngorm Store approach, gives you clarity from 35,000 feet before a deep dive and race along the terrain at the code level.
From a high level, there are four key challenges that the Adobe Consulting team typically faces, whether building mortgage calculators, stock trading applications, online banks, single-screen checkouts, full e-commerce applications, or interactive maps. These challenges are:
The challenges and requirements for the Cairngorm Store are no different.
In a typical online store, the client presents the products to sell to the customer, the shopping cart remembers what the user purchased, and the user must complete multipage application forms during the checkout process. Throughout this entire process, your application must maintain state on the client.
The single-screen user experience has a number of different on-screen components. It has both graphical and list-based views of the products for sale, detailed product information, a shopping cart that the user can add to or drag products to, and a series of forms that users must fill in to complete their orders. You must architect the view.
There are a number of features that users demand in the application. Users must be able to do the following:
These features drive your development.
Finally, the products that the application sells change as inventory changes. Your application must fetch product information from a server-side infrastructure capable of pulling products from an inventory database. Furthermore, when customers place orders, your application must persist the orders in enterprise systems by committing these orders to a database, SAP, or another back-end system. Your application must integrate with and invoke server-side services.
As you can see, the Cairngorm Store—though unique in its business domain, product selection, and look and feel—is just like any other rich Internet application when considering it in the context of these four key challenges.
In this article, we cover the first of these four key challenges: keeping state on the client.
The move from the development of traditional web applications to rich Internet applications often requires a shift in mindset because an RIA client can be entirely stateful.
Before the mainstream adoption of AJAX, web application developers were all too familiar with the stateless request/response approach. Moving on from somewhat crude approaches to maintain state on the client, such as continually stuffing data into an HTTP request as the client and the server exchange pages, or using URL encoding or cookies to keep some limited state information on the client, it became possible to twist the use of the HTTP request/response model to behave like a rich Internet application. No matter the approach used, the typical application developer's aim was to maintain state on the server, and make it available to the client.
With Flex RIAs, however, web application developers can break free from the shackles of HTTP request and response. You no longer have to concoct solutions for the stateless client because the client is indeed stateful. If you have been building desktop or rich-client applications with technologies such as Swing or AWT you'll shrug your shoulders right now because all of this will sound familiar—it is.
Many developers are familiar with the concept of MVC or Model/View/Control in application development, and wonder where state fits into this discussion. Quite simply, the state is the model. As obvious as this may sound, it presents additional common challenges to solve in the implementation of the model in an RIA.
First, resist the opportunity to scatter state all over your application as strings, numbers, Booleans, and all manner of other primitive objects. It is not my purpose here to promote best practice object-oriented (OO) development. Indeed, accepting the benefits and practicing the principles of OO are almost prerequisites to understanding this article.
However, we strongly advocate that the client hold the data—as state, model, or whatever you want to call it—as objects that have semantic meaning. What is semantic meaning in this context? It means, for example, that if you sell fly-fishing flies online, your object model should include "flies," "patterns," "hooks," and "hackles." If you sell mortgages, your object model should include "products," "rates," "repayments," and "terms." If you sell maps, your object model likely includes "routes," "waypoints," "points of interest," and "gas stations."
One challenge is relatively unique to the stateful client of a rich Internet application: The state on the client must reflect the state held on the other side of the wire, in the server middleware. Let's examine this design problem by considering a similar challenge that exists entirely on the server side.
In server-side application development, the state often exists within two tiers of an application architecture, specifically the business tier and the integration tier. The integration tier usually contains databases, mainframes, message queues, ERP solutions, CRM systems, and other enterprise information systems that hold data on the objects and process particular to a business domain. In enterprise application development, these systems are typically aggregated and presented to users through a middleware tier, also called the business tier.
Keeping state consistent across these two tiers is a challenge. First of all, the representation of data in these tiers may be dramatically different. For instance, the entities, relationships, views, and queries that exist in a relational database solution must be mapped from a relational model in a database to an object model in the middleware business tier. You can use technologies such as Enterprise JavaBeans, Hibernate, and ADO.NET to map data across these architectural tiers.
Irrespective of the solution chosen, the application developer assumes a great deal of responsibility in ensuring the consistency of state across tiers. As soon as you keep the same information in two places, there is a risk that changes made in one place are not reflected in the other, or that changes in one place overwrite changes made in the other. Suddenly, the developer must be concerned with concurrency, locking, transactions, rollback, and other approaches for ensuring that the model is consistent across the architecture.
As RIA developers, you are distanced from the complexities between the business and integration tier; instead, you are more concerned with ensuring ensure that the model in the business tier is consistent with the model in the presentation tier.
You need to ensure that you have a consistent object model on the client and server, and minimize the effort required to keep these object models synchronized. RIA developers use the Data Transfer Object pattern to do this.
First, consider how to best model the client data. Whenever you want to represent the value of things on the client, you can do worse than to create classes or objects that represent the values of those things. For instance, in Cairngorm Store, one thing that you must represent is the products in the store. Each product has a number of attributes: name, description, price, an associated image for the detailed view, and an associated thumbnail image for the catalog view. These attributes can together form a Product class.
These classes, or value objects, play a dual role in a rich Internet application. In addition to keeping the value of an item on the client, they are useful structures for exchanging or transferring data between different tiers in an application.
In server-side development, consider a strategy in which you query a database using SQL, receive a result set, and then transform that result set into a collection of value objects representing the things you have pulled from the database. By passing collections of these value objects between application tiers, you insulate each tier of the application from the underlying implementation of its neighboring tiers. The middleware need not care whether these value objects were created from an SQL query, stored procedure call, message queue, or web service call. By reusing these value objects, you decouple architecture by transferring data between tiers as objects with business meaning ("Products," "Fish," "Maps") rather than technical meaning ("RecordSets," "ResultSets," "Data Sets"). This has led developers to call the Value Object pattern the Data Transfer Object pattern instead.
We conceived Cairngorm before the term data transfer object (DTO) became popular, so we called—and continue to call—the business objects value objects or VOs.
For example, take a look at an example VO from the Cairngorm Store application:
package com.adobe.cairngorm.samples.store.vo
{
[RemoteClass(alias="com.adobe.cairngorm.samples.store.vo.ProductVO")]
public class ProductVO implements IValueObject, Comparable
{
public var id : Number;
public var name : String;
public var description : String;
public var price : Number;
public var image : String;
public var thumbnail : String;
…
}
}
This is straightforward. The ProductVO is nothing more than a collection of attributes that describe what makes a Product a product. Whenever you keep state on the client about Products, keep it as instances of the ProductVO class. If the application must know what the "currently selected product" is, store that information on the client as a ProductVO instance. If the application needs to retain a list of the products in a customer's shopping cart, store that information on the client as a list of ProductVO instances.
Furthermore, if you have to transfer products from the server (and we do, but more on that later) or if you ever have to transfer products back to the server, transfer that data back and forth as value objects. Others might say that you're using data transfer objects, because you are. They're the same thing, remember?
Let’s now consider how to transfer the object model between the server and client. When you develop middleware, a key artifact from the development process is typically a well-defined object model. In J2EE application development, there is often a package of Java value objects already on the server that correspond exactly to the value objects required on the client.
In this case, the Flex server does some pretty significant heavy lifting for the application. If you want to pass ActionScript value objects into Java, Flex can automatically create the equivalent Java objects on the server side for those objects that you placed on the client side. Similarly, as the Java middleware returns Java value objects to the client, Flex ensures that by the time the RIA uses these objects on the client side, Flex has already translated them into the equivalent ActionScript value objects.
Even more powerfully, if the objects are not just single value objects but complex graphs of value objects, Flex does all the work associated with traversing the graph of objects—not only translating the objects but maintaining the relationships between objects. Thus, when you have a ShoppingCartVO that contains a list of ProductVO objects, and each ProductVO contains a list of AddressVO objects (for delivery and invoice addresses) and an optional DiscountVO object, Flex ensures that the composition of objects stays consistent when passing it between the RIA client and middleware server.
To allow Flex to map between client-side and server-side value objects, you must provide some hints to the server about the relationship between the classes. In Cairngorm, you achieve this by making certain that every instance of a value object class contains the following:
[RemoteClass(alias="com.adobe.cairngorm.samples.store.vo.ProductVO")]
public class ProductVO implements IValueObject
This line ensures that a ProductVO.as class on the client maps to the Java class ProductVO.java that resides in the package com.adobe.cairngorm.samples.store.vo on the server. Note the use of the IValueObject interface provided by Cairngorm; we encourage you to use this marker interface to identify your value objects.
In a medium sized application containing a large number of VOs, it is easy to see why teams look for code generation techniques to reduce development time and cost. This approach can prove particularly useful when the number of value objects starts to escalate across multiple technology tiers. There are several well-known tools currently available that have proven successful at ActionScript code generation, including XDoclet2 and DAOFlex.
XDoclet2 is a Java-driven ActionScript code generator. Joe Berkovitz of Allurent created this labor-saving, open-source project for generating ActionScript value objects from their Java counterparts. You can find more information on the project, including a link to the source code on SourceForge, at http://xdoclet.codehaus.org/.
Another open-source tool that may meet your needs is DAOFlex, offered by Farata Systems. This utility can model a data access layer in ActionScript based on an SQL query. For more information and a tutorial on using this tool is available in the Adobe Developer Connection.
To recap, using value objects ensures that you do the following:
Getting data to the client that is consistent with the server is only the first part of the problem. The next part includes doing something with the data and deciding where to keep it.
One of the primary reasons you want data in the presentation tier of an n-tier application is to display that data. Furthermore, the R in RIA is about presenting data in a rich and immersive way.
To facilitate the display of data in an RIA, the Flex application framework provides a powerful feature known as data binding. Data binding allows one object in a pair of objects to act as a source and the other a destination. Changes to the source object are reflected immediately in the destination object. When the destination object is a visual component and the source object is client-side data, you suddenly have a powerful means for ensuring that changes to the client-side state are rendered to the user in real time. The user's view is never stale; the model (the client-side state) is always notified by the view (the user interface) if you have used data binding to bind the model and view together.
At Adobe Consulting, we have seen development teams produce convoluted solutions for updating the user interface whenever the underlying model changed. These teams became tangled in a mess pretty quickly when displaying data in different parts of the view in different ways. Consider the case in which you have a series of numbers that have been updated on a server. Depending on a user's location in the application, this may cause their table or graph display to update or it may update data in a dashboard summary about key assumptions for a business. Data Binding in Flex makes an easy job of an otherwise lengthy task that would require manual handling. By refactoring architecture towards keeping the data in a client-side model, and leveraging data binding between this model and the different view components, you eliminate much of the complexity around this kind of interactivity and responsiveness in an RIA.
Furthermore, we found that each developer kept the client-side state he needed for his application development tasks in his part of the code base. Consequently, when another developer needed to use that data in another part of the application, she had to create convoluted and complex solutions to ensure the data was visible from more than one place.
Often we found that developers referenced other data with long chains of component references, such as mx.core.Application.application.storeView.textualList.productGrid. Such strategies are incredibly brittle and difficult to change. For example, adding or removing any components from this list (such as storeView) would require a developer to update all other references to productGrid.
Additional strategies include passing data up and down through MXML component instantiations. Consider the following representation of MXML components, where data is in a leaf node of the tree and is required in another developer's leaf node. The only solution is to find a common branching point for both leaf nodes and then, from that branching point, pass the data down through the component chain with component instantiations, as follows:
<myView:MyComponent data="{this.data}" />
Once again, this is a strategy that is very brittle, and changes in the implementation of the view are quite difficult. It leads to regular errors when developers incorrectly pass data down the chain somewhere, or when they change views, causing the data to not reach its intended destination. Debugging these errors is a laborious job of tracing the component paths to confirm that data has been correctly passed between components. It's a frustrating task and not recommended.
Watching developers fall repeatedly into these traps, the Adobe Consulting team conceived the Model Locator pattern as a best practice for Flex developers. The Model Locator pattern is unique in Cairngorm because it is not borrowed from the Core J2EE Pattern catalog. Instead, we created this pattern specifically for Flex application development. Our motivation was to provide the ability to have Singleton model classes that hold state for the application and to enable this client-side state to reach the view classes.
It is important to ensure the model is a single instance, so data held in state can’t be duplicated or superseded by another instance. Multiple instances could lead to a number of data conflicts and should always be avoided. It is important to note, however, that an application is not limited to a single model; an application can have as many models as necessary, but only one instance of each.
Our Model Locator pattern strategy encourages the use of data binding so that view components bind directly to the client-side state held in the model. In this way, whenever the model is updated, all view components binding to the model receive notifications (through the underlying data binding mechanism) and update themselves to render the new model on the client.
In Cairngorm, we provide a marker interface for the Model Locator pattern, IModelLocator, to identify the models. Let’s focus on an example use of this pattern. In the Cairngorm Store we have a single class that implements the IModelLocator interface, com.adobe.cairngorm.samples.store.model.ShopModelLocator.
Take a few minutes to review the source for this ShopModelLocator class. In the Cairngorm Store, there is a notion of the "currently selected product":
public var selectedItem:ProductVO;
This is the item that the user has selected. The application stores an instance of a ProductVO in the Model Locator, uses the selectedItem property in the application to render the ProductDetails view, and uses the property to decide what to add to the shopping cart when the user presses the Add to Cart button.
In Part 3 of this series, you will learn how to use data binding to ensure that the currently selected item always appears in the product details view.
Additionally, through ShopModelLocator, you can find the list of products for sale in the Cairngorm Store by storing an ArrayCollection of these products on ShopModelLocator, as follows:
// ArrayCollection of ProductVOs
public var products:ICollectionView;
Finally, there is a sophisticated ShoppingCart component that encapsulates all the functionality associated with adding and removing items to a list of items that the customer may want to purchase. The shoppingCart instance is stored on the ShopModelLocator and initialized within the ShopModelLocator’s constructor.
Having the Model Locator as a simple implementation of a singleton ensures that one and only one instance of a ShoppingCart exists per user.
The single instance of ShopModelLocator is initialized immediately when the main application is initialized. The Main.mxml file (the entry point for the Cairngorm Store) declares the following method as a handler for the creationComplete event on the main Application tag:
[Bindable]
public var model:ShopModelLocator = ShopModelLocator.getInstance();
This code ensures that the entire client-side state (such as our shopping cart) correctly initializes when the application starts.
This brings up an important best practice. When you create components that rely upon client-side data, it is all too easy to create a direct reference to a Singleton model, such as ShopModelLocator. Indeed this is true throughout the application. We discourage this approach. Instead, consider passing the model and/or its properties down through a hierarchy of your view components, for a cleaner and more thoughtful solution.
As an example, the ProductDetails component in the Cairngorm Store requires only the currently selected item, so that it can display the name, description, price, and image of the item the user has selected. Because the currently selected item is stored in ShopModelLocator as an instance of the ProductVO called selectedItem, you can pass this item to the ProductDetails component as shown below:
<details:ProductDetails
id="productDetailsComp"
width="100%" height="325"
currencyFormatter="{ model.currencyFormatter }"
selectedItem="{ model.selectedItem }" />
The highlighted line of code shows that within the ProductDetails.mxml file, there is an attribute called selectedItem that is an instance of a Product VO. The code uses data binding (the curly brace notation) to ensure that anytime selectedItem on ShopModelLocator is updated, the ProductDetails view updates accordingly. we'll explore that mechanism more in Part 3.
In this article, we identified a number of challenges that consistently arise in the development of rich Internet applications, regardless of the business domain of the application. Throughout the series, we'll use these challenges to explore the Cairngorm development model in greater detail.
The first of these challenges is keeping state on the client; and it is important to note that the terms "state," "model," and "client-side data" are interchangeable in the development community. RIAs let you to break free of the request/response paradigm of HTML web applications. They enable you to keep data on the client as the user interacts with the application, but you must assume a certain degree of responsibility for that client-side state.
As you develop applications, you must address data integrity and consistency issues on the server—between the integration and business tier and, to a lesser extent, when sharing data between the business and presentation tier of an RIA. Remember that wherever an object model already exists on the client, you can take advantage of the Flex Server’s capabilities to map between the ActionScript and Java world, and keep a consistent object model on the client and server. Irrespective of this, representing client-side state as class objects with semantic meaning—rather than a mindless collection of strings, numbers, and Booleans—dramatically improves the readability and maintenance of your code base.
The Value Object (VO) pattern in Cairngorm, also referred to in the software engineering community as a Data Transfer Object (DTO) pattern, provides a sensible way of representing the client-side object model and helps developers make better decisions about where they keep that state on the client, how they share the client-side state between different components (that may have different ways of rendering the state), and how they ensure that the view is always showing the most up-to-date rendering of the state.
The Model Locator pattern, first introduced in Cairngorm, is a strategy for ensuring the state is held in a singleton class. This approach allows developers to pass client side state down through a chain of view components. The singleton nature of the Model Locator pattern ensures that you are not exposed to the risks of duplicate states on the client, gives a large development team a consistent place to share application-level state as instances of value objects, and enables developers to leverage the powerful data binding capabilities of Flex to ensure that the view is notified when the model has changed.
In Part 3, we'll focus on architecting the view, exploring in greater depth how to leverage Flex data binding with state stored on ShopModelLocator (the Model Locator implementation) while exploring more of the Cairngorm framework.
Developing Flex RIAs with Cairngorm microarchitecture – Part 3: Architecting the View