Professional Documents
Culture Documents
1: Introducing Cairngorm
Table of Contents
1. Introduction
2. Clarifying the Definition of Frameworks
3. Applying Design Patterns
4. A Short History of Cairngorm
• Printable version
• Send feedback
• Get an e-mail update of new articles
• Take a survey
•
Comments
Created:
13 February 2006
User Level:
Intermediate
This six-part series presents an open-source architectural framework to Flex developers called
Cairngorm. In this series I explain the thought leadership behind Cairngorm, the design challenges that
Adobe feels Cairngorm addresses best, and the projects for which Cairngorm is an appropriate skeleton
for development.
Using the Cairngorm Store sample application, this series explains what Adobe Consulting thinks about
scoping, estimating, and delivering a Rich Internet Application (RIA) when basing it on Cairngorm
from the start. I also explain the various Cairngorm concepts and take a deep dive into the
implementation of the Cairngorm Store.
Finally, I demonstrate some of the principal benefits of delivering an RIA based on this established
microarchitecture by adding a new feature to the existing Cairngorm Store application from the point of
view of a Cairngorm developer. By this stage in the series, you see the benefits for yourself.
Cairngorm isn't the only way to build a Rich Internet Application, of course. However, Adobe
Consulting has helped numerous customers and partners succeed in delivering large-scale Flex RIAs by
building upon their preexisting Flex application development knowledge using the information
contained in this series.
This comprehensive introduction covers the full spectrum of Cairngorm, from understanding the
motivation and concepts of Cairngorm to architecting your own applications upon this established and
supported microarchitecture.
Instead of delving into code from the outset, Part 1 offers a context and background for understanding
the Cairngorm architecture. I discuss frameworks and clarify the difference between an application
framework and an architectural framework. I then explore design patterns and introduce the
microarchitecture concept. Finally, I give a brief background on the emergence of Cairngorm: its
history and where it is headed—its roadmap.
In Parts 2–6, you will develop a retail commerce application using both Flex and Cairngorm on the
client-side tier and a new or existing J2EE infrastructure on the server-side tier.
Requirements
Application Frameworks
Flex is a tremendous example of an application framework. In fact, the forthcoming release of Flex 2.0
actually distinguishes the application framework piece—often called the "app model" within Adobe—
in its architecture. The Flex Framework 2.0 provides a rich collection of class libraries that provide
highly granular functionality that developers can use to create custom code. For instance, the Flex 2.0
Collections API provides application developers with all the base-level functionality needed to create
managed data collections. Application developers then compose these collections together into higher-
level objects that are relevant to their particular application. Furthermore, application frameworks such
as Flex typically expose application-level services such as history management, layout management,
cursor management, exception handling, internationalization, logging, and so forth.
When a framework provides highly granular class libraries that give a high degree of flexibility to
application developers, or when a framework provides application-level services that are useful across
multiple developer projects, I call it an "application framework."
Another excellent example of an application framework is the FAST framework that Adobe Consulting
has used with customers and partners with great success. The FAST framework, as John Bennett
explains in his article, Faster Development with the Flex Application Starter Toolkit (FAST), provides
application services for logging and tracing, and value-add class libraries that extend the Flex
Framework's own implementation of RPC data services in Flex 1.x.
Architectural Frameworks
Architectural frameworks are different beasts entirely. Typically the job of an architectural framework
is not to provide any additional services to application developers except an infrastructure around
which an application can hang—to provide the skeleton or internal structure of moving parts, around
which the flesh and muscle particular to a business domain can be layered.
In other words, an architectural framework provides a generic starting point for the technical
architecture of your application.
Table of Contents
1. Introduction
2. The Cairngorm Store: Four Key Challenges
3. Keeping State on the Client
4. Keeping a Consistent Object Model Between Flex and Java
5. Binding the Model and View Together
6. Enter the Model Locator Pattern
• Printable version
• Send feedback
• Get an e-mail update of new articles
• Take a survey
•
Comments
Created:
20 February 2006
User Level:
Intermediate
In Part 1 of this series, I introduced Cairngorm, a lightweight technical architecture that simplifies
much of the recurring complexity in developing large-scale rich Internet applications, called enterprise
RIAs.
In this article, I describe the challenges faced in developing enterprise RIAs—specifically, how to keep
state on the client. I explain this in the context of an e-commerce application called the Cairngorm
Store. You'll learn about two fundamental patterns in the Cairngorm architecture: the Value Object
pattern and the Model Locator pattern. By the end of this article, you will understand the clarity you
can achieve within your own Flex application development by using these patterns.
Requirements
To complete this tutorial you may want to install the following software and files so that you can follow
along:
Macromedia Flex
• Try
• Buy
This is not rocket science. 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. 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?
This line ensures that a ProductVO class on the client maps to the Java class ProductVO.java that
resides in the package org.nevis.cairngorm.samples.store.vo.ProductVO on the
server.
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 ModelLocator, you can find the list of products for sale in the Cairngorm
Store from anywhere in the application by storing an ArrayList of these products on ModelLocator,
as follows:
public static var products : Array; // containing ProductVO objects
Also notice how the application stores constants in ModelLocator. In this application, these
constants allow you to track the state of the application (there are three possible states) as follows:
public static var workflowState : Number;
//------------------------------------------------------------
Finally, you have a complex 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.
You stored the shoppingCart instance on the ModelLocator and initialized it within the
ModelLocator in the initialise() method.
Having all the attributes on the Model Locator pattern as static attributes ensures that the Model
Locator pattern is a simple implementation of a singleton. You ensure, for instance, that one and only
one instance of a ShoppingCart exists per user.
The single instance of ModelLocator is initialized immediately when the main application is
initialized. In the Main.mxml file (the entry point for the Cairngorm Store) you have declared the
following method as a handler for the creationComplete event on the main Application tag:
<mx:Script>
<![CDATA[
import org.nevis.cairngorm.samples.store.model.ModelLocator;
]]>
</mx:Script>
This code ensures that all the client-side state (such as our shopping cart) correctly initializes when the
application starts.
Next, I'll take a look at data binding to the ModelLocator in greater detail. But to whet your
appetite, let me say that when you create components that rely upon client-side data rather than adopt
any of the convoluted strategies that result in a brittle implementation of the view, you can instead
locate client-side state by binding to the singleton ModelLocator from any component in the
application.
By way of example, we have a SideArea.mxml component in the Cairngorm Store that contains the
ProductDetails and ShoppingCart components. As you'll see in Part 3, ProductDetails requires 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 ModelLocator as an instance
Product VO called selectedItem, you can pass this item into our ProductDetails component
as shown below:
<details:ProductDetails
id="productDetailsComp"
width="100%" height="325"
currencyFormatter="{ ModelLocator.currencyFormatter }"
selectedItem="{ ModelLocator.selectedItem }"
addProduct="addProductToShoppingCart( event )" />
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. You have just used data binding (the curly brace
notation) to ensure that anytime selectedItem on ModelLocator is updated, the
ProductDetails view updates accordingly. I'll explore that mechanism more in Part 3.
Steven Webster
Adobe Consulting
www.richinternetapps.com
Table of Contents
1. Introduction
2. Architecting the View
3. Binding the Model to the View
4. Best Practices for MXML Development
• Printable version
• Send feedback
• Get an e-mail update of new articles
• Take a survey
•
Comments
Created:
27 February 2006
User Level:
Intermediate
In Part 2 we focused on managing state within rich Internet applications as you learned how to use the
Value Object and Model Locator patterns. In Part 3, the aim is to reach out to your application's users.
While the concept of holding state on the client empowers developers, it means nothing to users unless
they can visualize and interact with that state in a meaningful way.
The Cairngorm Store must display product images so that users can select products and get further
details on them, drag and drop products into their shopping cart, and purchase these products in a
checkout process. In this article, you'll explore how to architect the view and deliver rich and
immersive experiences to your users using the Cairngorm framework. You'll pick up some best
practices you can use to structure your MXML code and files, even though Cairngorm does not
prescribe any specific way to structure your view.
Requirements
To complete this tutorial you may want to install the following software and files so that you can follow
along:
Macromedia Flex
• Try
• Buy
Cairngorm version .99
• Download Cairngorm version .99
Prerequisites
Read the previous articles in the series, starting with Developing Flex RIAs with Cairngorm
Microarchitecture – Part 1: Introducing Cairngorm before reading Part 3.
Page 1 of 4
Next Page
Open the ProductDetails.mxml file to see the implementation of the component. Note the judicious use
of data binding to render the individual elements of the selectedItem instance:
<mx:Script>
<![CDATA[[
]]>
</mx:Script>
<mx:HBox>
<mx:Canvas
width="150" height="140"
clipContent="false">
<mx:Image
id="image"
source="{ selectedItem.image }"
mouseDown="beginDrag();"
mouseOverEffect="big" mouseOutEffect="small" />
</mx:Canvas>
<mx:VBox
width="100%" height="100%"
styleName="productDetailsTitle">
<mx:Label
id="name"
text="{ selectedItem.name }"
styleName="title" />
<mx:Label
id="price"
text="{ selectedItem.price}"
styleName="price" />
</mx:VBox>
</mx:HBox>
<mx:Text
id="description"
width="100%" height="100%"
text="{ selectedItem.description }"/>
</mx:VBox>
With these data bindings in place, any changes to the selectedItem instance held in
ModelLocator causes the binding to execute in the <details:ProductDetails /> instance,
updating the selectedItem attribute of the ProductDetails component. This update causes
each binding on selectedItem in ProductDetails to execute, resulting in an update to the
image, name, description, and price to reflect the attributes of the newly selected item.
This is the basic premise of Flex data binding. Cairngorm brings an approach for ensuring that dynamic
data displayed in your MXML comes from ModelLocator, not elsewhere.
The final piece of the puzzle is how to update the selectedItem instance in ModelLocator. The
ProductsAndCheckoutViewStack component shows the following component instantiations:
<productview:GraphicalProductList
id="graphicalProductList"
products="{ ModelLocator.products }"
selectedItem="{ ModelLocator.selectedItem }"
select="ModelLocator.selectedItem = event.target.selectedItem;" />
<productview:TextualProductList
id="textualProductList"
products="{ ModelLocator.products }"
selectedItem="{ ModelLocator.selectedItem }"
select="ModelLocator.selectedItem = event.target.selectedItem"
currencyFormatter="{ ModelLocator.currencyFormatter }" />
Both components responsible for graphical and textual rendering of the product catalog expose select
events, which the components dispatch when the user clicks a new product. To handle these events, as
shown in the highlighted code above, simply update the selectedItem instance on
ModelLocator to a ProductVO value object representing the item that the user has just selected.
Since the model notifies the view of this change through data-binding means that the application
developer does not need to worry about implementing of the view or updating the many different
elements of the view to reflect the model change.
See this demonstrated in the "single model, multiple view" example by looking more closely at the two
components above: the GraphicalProductList and the TextualProductList components.
In the instantiation of these components (both have a products attribute), both components bind their
individual products attributes to the same model; that is, to ModelLocator.products.
ModelLocator.products is an array list of ProductVOs, as you may recall from Part 2 (or by
reviewing the source code).
In the GraphicalProductList component, this list of products becomes the dataProvider for
a TileList, which has its own cell renderer (ProductThumbnail) capable of rendering a
ProductVO:
<mx:TileList
id="tileListComp"
width="100%" height="100%"
dataProvider="{ products }"
cellRenderer="org.nevis.cairngorm.samples.store.view.productview.ProductThumbna
il"
itemWidth="120" itemHeight="116"
dragEnabled="true"
change="updateSelectedProduct( event );"
borderStyle="none" />
Meanwhile, the TextualProductList component does nothing more than pass the list of ProductVOs to
the dataProvider of a DataGrid, which renders the list of ProductVO instances accordingly:
<mx:DataGrid
id="dataGridComp"
dataProvider="{ products }"
change="updateSelectedProduct( event );"
dragEnabled="true"
width="100%" height="100%">
This example clearly demonstrates the Model Locator strategy in action. Each view component
declares and renders its own local model, so that each is completely oblivious to the application
running within a Cairngorm architecture. Only at the very highest level of the component's usage—
most often in the entry point MXML file—do you couple the component's usage to Cairngorm by
populating and binding its local model to a model residing on ModelLocator.
<mx:Metadata>
[Event("addProduct")]
</mx:Metadata>
<mx:Script>
<![CDATA[
: : :
<mx:Button
label="Add to Cart"
click="addProduct();" />
</mx:Panel>
Here the click event of the button is mutated into an addProduct event out of the component.
Furthermore, the event has the selectedItem (the product to add) and the quantity attribute
associated with it on the ProductDetails component, describing how much of the product the user
wishes to add to the shopping cart.
Consequently, when you create an instance of the ProductDetails component (in SideArea.mxml)
you can handle the event in a much more meaningful way:
<details:ProductDetails
id="productDetailsComp"
width="100%" height="325"
currencyFormatter="{ ModelLocator.currencyFormatter }"
selectedItem="{ ModelLocator.selectedItem }"
addProduct="addProductToShoppingCart( event )" />
Although this advice is in no way specific to Cairngorm application development, following this advice
improves the clarity of your application development and makes it easier for you to recognize
integration points between your MXML and ActionScript implementations of the view and any
underlying business logic applied throughout the Cairngorm skeleton.
Steven Webster
Adobe Consulting
www.richinternetapps.com
Table of Contents
1. Introduction
2. Engaging in Rich Conversations
3. The Service to Worker Microarchitecture
4. Service to Worker: Mapping User Gestures and System Occurrences to Events
5. Service to Worker: Listening for Events with the Front Controller
6. Service to Worker: Broadcasting Cairngorm Events with the Event Broadcaster
• Printable version
• Send feedback
• Get an e-mail update of new articles
• Take a survey
•
Comments
Created:
6 March 2006
User Level:
Intermediate
Part 3 explained how to construct the user experience using MXML and ActionScript and, most
importantly, explored an area of application development where Cairngorm imposes itself least—and
where it provides the greatest degree of flexibility. After all, the user experience differentiates rich
Internet applications (RIAs) from other applications, and any attempt to impose a structure on
constructing the view limits the richness of the application.
If a rich Internet application is a conversation between man and machine, then Cairngorm is the
interpreter. So far, I have described how Cairngorm enables the machine to talk to the user as it renders
the application's state in a dynamic, visual way. Now consider the more important aspect of
communication: listening (rather than talking). In this article, I explore the design patterns that
Cairngorm uses to listen to the user; through user gestures, I describe how Cairngorm understands what
the user wishes to do in the application.
In the Cairngorm Store, a user can browse a product catalog, select products, get details on selected
products, drag products into a shopping cart, and purchase these products through a checkout process.
Now you'll learn how Cairngorm encourages you to breathe interactivity into rich and immersive
experiences, and how Cairngorm enables your application to respond to your user's every gesture,
whether through a mouse click or keyboard stroke.
This article explores the most fundamental and innovative patterns of the Cairngorm microarchitecture:
the Front Controller, Event Broadcaster, and Command patterns. Understanding how they collaborate
will help you with rapid and consistent application development of RIAs with Flex.
Requirements
To complete this tutorial you may want to install the following software and files so that you can follow
along:
Macromedia Flex
• Try
• Buy
Prerequisites
Read the previous articles in the series, starting with Developing Flex RIAs with Cairngorm
Microarchitecture – Part 1: Introducing Cairngorm before reading Part 4.
Engaging in Rich Conversations
When we talk about rich Internet applications, we understand the word rich to describes the
dramatically improved user experience beyond a traditional web application. There is, however, also a
richness in the level of interactivity in the application. Interactivity in this case does not mean
animation and effects, but the extent to which a user can engage with the application as it engages with
the user.
Consider a conversation through a translator. As one person speaks, the translator listens and then
translates a small part of the conversation to a third party. Meanwhile, the speaker waits while the third
party responds, also through the translator. This ineffective communication process repeats itself
between the two parties.
This is analogous to a web application, which is a similar conversation between man and machine. The
web application translates through the stuttered medium of browser to user. Then the user clicks a
button to communicate back to the web application, also through a browser—an ineffective process for
communication.
One of the key benefits of a rich Internet application—beyond the obvious rich user experience—is the
rich communication that can occur between man and machine. Freed from the limits of the
request/response page-driven browsing metaphor, the user can interact at his or her own pace with the
application while the application responds—also at its own pace—to the user. Because control never
passes between man and machine, communication flows better, creating simpler, easier, more effective
and, ultimately, more enjoyable user experiences.
One of the key innovations in Cairngorm leverages a collaboration of design patterns that the J2EE
community used to centralize and manage the man/machine communication in the old-fashioned web
application world and bring it up to date for the more seamless, flowing conversations that rich Internet
applications provide. These design patterns work so well together at solving the higher level problem of
achieving rapport between user and application that they are considered their own microarchitecture:
the Service to Worker microarchitecture.
class org.nevis.cairngorm.samples.store.command.AddProductToShoppingCartCommand
implements Command
{
public function execute( event : Event ):Void
{
var product : ProductVO = ProductVO( event.data.product );
var quantity : Number = Number( event.data.quantity );
ModelLocator.shoppingCart.addElement( product, quantity );
}
}
It's not very complicated, is it? First of all, see how the application-specific Command class
implements the Cairngorm Command interface. If you were to view the source code for Cairngorm,
you would see that this interface simply enforces that the command must have a method called
execute() that can act as its entry point. This allows Cairngorm to execute each and every
command without caring about what the command actually does.
If you look at the implementation of the execute() method, you can see how the event that caused
Cairngorm to execute the command contains a ProductVO value object and a quantity in the data
payload of the Event class. The Event class is also a Cairngorm class that gives each Cairngorm
event a type (such as the addProduct type) and some associated data (such as the product and the
product quantity to add to the shopping cart).
Because the shopping cart is a client-side state, it resides on the ModelLocator class. Therefore, the
command has only to carry out the work that the user requests and add it to the appropriate amount for
the product in the shopping cart, using the methods on the ShoppingCart class.
That's it! There's minimal work required to create a simple feature of the Command class. The
command queries the event, extracting any data associated with the event so that it can perform its task.
If executing the command affects the state of the application—such as requiring a new product to
appear in the Shopping Cart view, for instance—the application accomplishes the task by updating the
client-side state through ModelLocator. If you bind views correctly to ModelLocator, the user
experience will reflect these state changes accordingly.
Creating Helper Classes in Cairngorm
There is a very important design point to emphasize here. In the preceding example, all complex
business logic associated with what a shopping cart can and cannot do is encapsulated in a class called
the ShoppingCart class. If a user adds a product to the cart, for instance, the ShoppingCart class
adds it to the cart if it doesn't exist already. If the product already exists in the cart, the class increases
in quantity.
Cairngorm does not relieve the developer of the responsibility of creating his or her own business
objects and classes on the client. Indeed, what makes an application specific to its business domain is
its classes.
Developers should constantly seek to extract these classes from the Cairngorm architecture, pulling
business logic out of commands and into classes, a classic implementation of Extract class refactoring.
The beauty of this technique is that you can unit-test these classes extensively, document their APIs,
and make them available for reuse to other application developers without any dependency on the
surrounding Cairngorm application.
The ShoppingCart class in Cairngorm Store is an excellent example of this design principle. Apply
it at every opportunity in your own development.
//----------------------------------------------------------------------------
//-------------------------------------------------------------------------
First, recognize the best-practice approach of naming all events as constants on the
FrontController instance, ShopController. A common development error is to set up the
controller to listen for an event called addProductToShoppingCart but then, when broadcasting
the event, mistakenly refer to the event as addProduct. Consequently, you think you have broadcast
a valid event and caused a Command class to execute; instead, FrontController refuses to invoke
any commands and does not recognize the event name. However, these errors manifest at runtime only,
as you broadcasting events such as
ShopController.EVENT_ADD_PRODUCT_TO_SHOPPING_CART. The compiler catches any
mistyped events noisily at compile time, rather than failing silently at runtime. While Cairngorm does
not require this, it's a best practice that we highly recommend.
Next, notice how the constructor calls the initialiseCommands() method in the controller,
ensuring that when the application does create the controller, it creates it with the full knowledge of
which commands it can delegate work to, depending on which events it hears being broadcast.
Let's look at the example of adding products to the shopping cart. When the app broadcasts the
ShopController.EVENT_ADD_PRODUCT_TO_SHOPPING_CART command, the following
entry in the controller ensures that the execute() method on the
AddProductToShoppingCartCommand is invoked:
addCommand( ShopController.EVENT_ADD_PRODUCT_TO_SHOPPING_CART,
new AddProductToShoppingCartCommand() );
By extending the base FrontController class in the Cairngorm framework, you can use the
addCommand() method of the FrontController class to register events with corresponding
command classes. The underlying Cairngorm architecture does the rest of the work. It simply
broadcasts the appropriate event from anywhere in your application and Cairngorm ensures that the
relevant command is invoked.
You create your controller in the main entry point for MXML. In the Cairngorm Store, this is in the
Main.mxml file, as follows:
<control:ShopController id="controller" />
The "control" namespace was defined in the Application tag as pointing at the appropriate
Cairngorm package:
xmlns:control="org.nevis.cairngorm.samples.store.control.*"
This is all you need to do to ensure that the application has a Front Controller pattern that responds to
all the events and invokes all the commands that you registered with the addCommand() method.
Look at the instantiation of ProductDetails in the SideArea component. See the following
code:
private function addProductToShoppingCart( event : Object ) : Void
{
EventBroadcaster.getInstance().broadcastEvent(
ShopController.EVENT_ADD_PRODUCT_TO_SHOPPING_CART, event );
}
This is another best practice we recommend: Providing simple methods that describe the broadcasting
of an event in business terms. In this way the instantiation of the ProductDetails component is as
follows:
<details:ProductDetails
id="productDetailsComp"
width="100%" height="325"
currencyFormatter="{ ModelLocator.currencyFormatter }"
selectedItem="{ ModelLocator.selectedItem }"
addProduct="addProductToShoppingCart( event )" />
Essentially, the ProductDetails component exposes an addProduct event that contains the
product and quantity that the user gesture indicated adding. You take this event and broadcast it to the
FrontController class using the Event Broadcaster as
EVENT_ADD_PRODUCT_TO_SHOPPING_CART. With those few lines of code, you can now entrust
Cairngorm to ensure that AddProductToShoppingCartCommand is invoked, doing all the work
necessary to update the client-side model through ModelLocator, which will also result in the
ShoppingCart view (which binds to the ShoppingCart model in ModelLocator) updating to
show the new state of the shopping cart.
Now you can recognize how the Service to Worker microarchitecture (Front Controller, Event
Broadcaster, and Command patterns) collaborates with the Model Locator and Value Object patterns to
handle an entire conversation between the user and the rich Internet application.
All the drag-and-drop "view logic" is encapsulated in the components for the drop source and drop
target. It is of no consequence to Cairngorm how this is implemented. Quite simply, to hook the
operation up to the underlying Cairngorm application, you simply broadcast the
ShopController.EVENT_ADD_PRODUCT_TO_SHOPPING_CART event as a consequence of the
appropriate user gesture. In this case, it is the drag-and-drop operation.
Steven Webster
Adobe Consulting
www.richinternetapps.com
Table of Contents
1. Introduction
2. Integrating with Flex RPC Services
3. Locating Services with the Service Locator
4. Proxying Services with the Business Delegate
• Printable version
• Send feedback
• Get an e-mail update of new articles
• Take a survey
•
Comments
Created:
13 March 2006
User Level:
Intermediate
In Part 4 of this series, you learned about the client-side features of the Cairngorm application. I
advocated some best practices for structuring and architecting your view—the MXML and
ActionScript code that describes your user experience. I then dived into the details of feature-driven
development using the Front Controller, Event Broadcaster, and Command patterns, explaining how
they assist in rapidly and consistently delivering new functionality to the evolving rich Internet
application (RIA) development.
The final piece of the RIA puzzle that Cairngorm helps you solve is integrating your application with
services that reside on "the other side of the wire"—that is, on the server infrastructure. In the same
way that you desire a consistent approach to implementing client-side business logic, you'll benefit
from a consistent means of invoking business logic on a server and fetching data from a server for your
client-side application.
By the end of this article, you will have been fully exposed to the collaboration of design patterns in a
Cairngorm application and will be able to explore and understand the implementation of the other
features of the Cairngorm Store more fully.
Requirements
To complete this tutorial you may want to install the following software and files so that you can follow
along:
Macromedia Flex
• Try
• Buy
<mx:RemoteObject
id="productService" named="productServiceImpl"
protocol="http"
showBusyCursor="true"
result="event.call.resultHandler( event );"
fault="event.call.faultHandler( event );">
</mx:RemoteObject>
<mx:RemoteObject
id="creditCardService" named="creditCardServiceImpl"
protocol="http"
showBusyCursor="true"
result="event.call.resultHandler( event );"
fault="event.call.faultHandler( event );">
</mx:RemoteObject>
</cairngorm:ServiceLocator>
As you can see here, there are two services: one called productService that you use to fetch
products from the server-side database, and the other called creditCardService that you use to
validate credit card information on the server before approving orders.
Both services are implemented as named services. This means that the information about the actual
Java class is held in the flex-config.xml descriptor file on the server; it is not exposed in any client-side
code. This is a security best practice. It's best not to reveal things like class names that might give a
determined hacker who decompiles the Flex RIA some hints as to the implementation of your server-
side code.
When declaring services in Flex, always specify result and fault handlers. The result handler is the
method called when a service call returns some data; likewise, the fault handler is called when a service
call fails.
Because the Service Locator anticipates that its services will be used multiple times in a multitude of
different ways, it makes no assumptions about how to handle any results that are returned. Instead, the
Service Locator assumes that the result handler and the fault handler will belong to the class called the
service. It achieves this by specifying event.call.resultHandler and
event.call.faultHandler as the handlers through a Flex object know as the pending call.
I won't dive into the details of pending calls in this article because one of the purposes of the Service
Locator is to abdicate the developer from any responsibility of understanding implementation details of
services such as the PendingCall object.
Needless to say, when you specify services, do so by declaring them in a Services.mxml file using the
appropriate MXML service tag—and always specify the result and fault handlers as follows:
result="event.call.resultHandler( event );"
fault="event.call.faultHandler( event );"
Where the business namespace is declared and specifies the package in which Services.mxml resides:
org.nevis.cairngorm.samples.store.business.
Having declared your Service Locator, you could then place the following code into your
GetProductsCommand command if you so desired:
class org.nevis.cairngorm.samples.store.command.GetProductsCommand implements
Command
{
In this code, you can see how you use the Service Locator to fetch a service by name and then invoke
methods on the service. The code shows that there is no requirement for developers to know whether
this is a WebService, RemoteObject, or HTTPService RPC service; they simply ask for the service
from the Service Locator and then invoke methods on it.
Now you could refine this code further, so that it specifies the result handler on the Command class as a
method belonging to GetProductsCommand. This would allow the Command class to invoke the
service and handle the result in the context of the Command class.
However, while this would be a more than acceptable solution, there is a final layer of abstraction that
we have found very useful. We recommend it as a starting point for any Cairngorm application.
Let's take a look at the final pattern that Cairngorm advocates: the Business Delegate.
Page 3 of 4
Previous Page
Next Page
Comments (1)
• Anirudh Sasikumar(April 24, 2008)
Tip for Flex 2 and 3 developers:
//---------------------------------------------------------------
//---------------------------------------------------------------
private var responder : Responder;
private var service : Object;
}
A discussion of the Business Delegate makes the most sense in the context of a Command class that
calls the delegate; in the case above, the GetProductsCommand class:
class org.nevis.cairngorm.samples.store.command.GetProductsCommand implements
Command, Responder
{
public function execute( event : Event ):Void
{
var productsNotLoaded = ( ModelLocator.products == null );
if( productsNotLoaded )
{
var delegate : ProductDelegate = new ProductDelegate( this );
delegate.getProducts();
}
else
{
// products already loaded, so no need to load them again
return;
}
}
//----------------------------------------------------------------------------
ModelLocator.selectedItem = products[ 0 ];
ModelLocator.products = products;
ModelLocator.workflowState = ModelLocator.VIEWING_PRODUCTS_IN_THUMBNAILS;
}
//----------------------------------------------------------------------------
public function onFault( event : Object ) : Void
{
mx.core.Application.alert( "Products could not be retrieved!" );
}
}
Let's explore the interaction of these two classes in greater detail. The Command class creates a new
instance of the Product Delegate pattern, passing itself ("this") to the constructor of the delegate.
This is the Command class' way of saying "I want to handle the results of any calls made by the
delegate"—something the delegate calls the "responder" (the object wishing to respond to any results).
By electing to act as a responder, the Command class implements the Responder interface in
Cairngorm; this ensures that the developer adds an onResult() method to handle any delegate
results and an onFault() method to handle any delegate faults.
If you then look at the implementation of getProducts() on the Product Delegate, there are two
lines of boilerplate code that you can add for each and every delegate method:
public function getProducts() : Void
{
var call = service.getProducts();
call.resultHandler = Delegate.create( responder,
responder.onResult );
call.faultHandler = Delegate.create( responder,
responder.onFault );
}
Don't worry too much about the details of this incantation. These lines ensure that the results and faults
from the server are routed back to the responder—in this case, to the the Command class.
With this collaboration of classes in place, the flow of control is both simple and elegant:
• Command class creates a business delegate
• Command class calls business methods, such as getProducts(), on the delegate
• Delegate worries about looking up the service and invoking the service
• At some point in the future, the server returns results to the delegate
• Delegate immediately passes these results to the onResult() method on the command
If you look at the onResult() method of GetProductsCommand, it extracts the array of products
from the event representing the server results and stores them on the ModelLocator, while also
setting selectedItem on the ModelLocator to the first product in the catalogue.
If any other commands have utility for a getProducts() command, they can also call the Business
Delegate method. On large development teams, developers learn to check the delegates to see if any
other developers have implemented methods that they can reuse before invoking the services
themselves.
Incidentally, the server-side implementation of getProducts() is a Java class that employs a data
access object (DAO) to query a relational database (either MySQL or HSQLDB are supported) using
JDBC. Sound complicated? Well, it doesn't matter; that's the entire point of the Business Delegate: to
hide the complexity of server-side implementation from client-side developers. The source is there if
you care.
Stubbing the Business Delegate
There's another great reason for using a Business Delegate pattern. Often, there is one set of developers
responsible for the server-side development of an RIA and another team working on the Flex client.
The Business Delegate class becomes an agreed "contract" between both teams, as the single point of
interface between client and server.
We have often consulted on projects where development has started with a Business Delegate that
doesn't actually call any server-side services. Instead, the Business Delegate is a "stub" class: It creates
mock value objects in ActionScript and directly calls the onResult() method on the calling
command.
Client-side developers can then call the Business Delegate, oblivious to the fact that the data returned
to them is dummy data. Once server-side development is in sync with client-side development, you can
change the method calls on the Business Delegate to call all the way through to the server, with no
further changes required in the Command classes, or in any of the MXML or ActionScript view code.
When working with Cairngorm, many developers wonder about the utility of the Business Delegate
and whether their commands might not just as easily call services using the Service Locator. While it is
certainly our experience and preference to have Command classes oblivious to the concept of server-
side services by employing the business delegate as a proxy, the flexibility of Cairngorm means that
developers can choose not to have Business Delegates if they so prefer.
However, we find that the minimal amount of effort necessary to create these delegates—coupled with
the fact that we usually find ourselves refactoring towards a Business Delegate anyway—to be sound
reasons for including the Business Delegate pattern in all applications that we architect upon
Cairngorm.
Steven Webster
Adobe Consulting
www.richinternetapps.com
Table of Contents
1. Introduction
2. Cairngorm Architecture Review
3. Cairngorm Flow of Control
4. Cairngorm Store Wish List
5. In Perspective
• Printable version
• Send feedback
• Get an e-mail update of new articles
• Take a survey
•
Comments
Created:
27 March 2006
User Level:
Intermediate
In the final article of this series, let's take a moment to stand on the summit of Cairngorm and look
down at how far we have climbed.
Having acquired some development experience over the last series of articles, you now have the
knowledge, tools, and expertise to be able to scale any complex rich Internet application, applying the
lessons you have learned to tackle any application development project safely and securely, no matter
how steep the problem may first appear.
By applying these lessons, you can consistently, effectively, and rapidly accomplish the design,
development, and delivery of rich Internet applications with the confidence that the methods you use
have been proven effective on numerous challenges as complex and critical as your own.
Let's consolidate this understanding with a quick review of the different patterns in Cairngorm and how
they work together. Recall the essential infrastructure that you must put in place so that you can move
towards your goal by delivering feature after feature into your application.
Finally, let's set ourselves a development task in the Cairngorm Store—and use the exercise to confirm
that you are now ready to develop your own applications with Cairngorm by correctly applying the
same best practices that so many other Flex developers use with success.
Requirements
To complete this tutorial you may want to install the following software and files so that you can follow
along:
Macromedia Flex
• Try
• Buy
Prerequisites
Read the previous articles in the series, starting with Developing Flex RIAs with Cairngorm
Microarchitecture – Part 1: Introducing Cairngorm before reading Part 6.
Store State in the Model Locator and Let Model Notify View
Finally, your application must be able to communicate back to the user. In Cairngorm, the Model
Locator is a single place that holds the entire application state. Though we care little about how the user
experience is implemented with MXML and ActionScript, we do insist that anywhere the view is
dynamic that the data for this view uses data binding to the Model Locator.
The Model Locator contains only Value Objects. When your Command classes ask the Business
Delegate to call some server-side business logic on your user's behalf, they implement the Responder
interface and receive either Value Objects or collections of Value Objects in the onResult() method.
The very last part of the converstation in a Cairngorm application results in the command updating the
Model Locator with any new state represented by these Value Objects. With the Model Locator using
data binding to notify the view, the user interface updates to display any new data and completes the
conversation with the user.
Note that this requirement doesn't dictate how the customer might move objects between the Wish List
and the checkout. Let's assume for now that this requirement is an additional story.
Before the user experience gods strike us down, it's worth also noting that the choice of a tabbed
interface is a functional one as implemented by a J2EE developer for the purposes of this
demonstration. In a real-world project, I would use the capabilities of a user experience consultant to
incorporate the Wish List seamlessly and effectively into the overall information architecture!
As is typical in an agile development, a developer usually breaks a story down into tasks. With a
Cairngorm microarchitecture in place, the tasks on any given feature become quite predictable and
repeatable. You may remember I discussed feature-driven development in Part 4 of this article series.
For the Wish List story above, I suggest the tasks in the story are as follows:
For the Wish List story above, I suggest the tasks in the story are as follows:
• Register a new addProductToWishList event with the Front Controller class.
• Create a new AddProductToWishList command to handle the event.
• Create a WishList object that resides within ModelLocator.
• Implement the View:
• Add the WishList object into a Tab Navigator control next to the Shopping Card.
• Dynamically bind the WishList object to ModelLocator.
• Broadcast user gestures to FrontController when the user presses the Add to Wish List
button.
• Broadcast user gestures to FrontController when the user drags and drops into the
WishList object.
Let's follow the code associated with each of these tasks!
Remember, by using a static constant to name our events, you introduce compile-time checking that the
events broadcasted are valid ones. This ensures that your application does not fail silently when you
inadvertantly spell the name of the event incorrectly before broadcasting it into a black-hole!
Next, you must register a Command class that will assume responsibility for carrying out the work
associated with this event. Do this by adding a new entry to the initialiseCommands() method
as follows:
addCommand( ShopController.EVENT_ADD_PRODUCT_TO_WISH_LIST, new
AddProductToWishListCommand() );
That's it as far as the Front Controller class instance is concerned. Now it knows of the new event, and
it knows what to do when the event occurs.
So next, you have to create the Command class that you have delegated as responsible for this event:
the AddProductToWishList Command class.
class org.nevis.cairngorm.samples.store.command.AddProductToWishListCommand
implements Command
{
public function execute( event : Event ):Void
{
var product : ProductVO = ProductVO( event.data.product );
var quantity : Number = Number( event.data.quantity );
ModelLocator.wishList.addElement( product, quantity );
}
}
There's nothing complex at all about this code ... the execute() method is the command-
independent entry point that the Front Controller calls whenever a
ShopController.EVENT_ADD_PRODUCT_TO_WISH_LIST occurs.
The execute() method simply adds the appropriate number of the product involved in the user's
request to "add to wish list" to the WishList object held on ModelLocator.
And again, that's it. You can assume for now that the user interface, the view, are bound through Flex
data-binding to the WishList object held on ModelLocator. That's all there is to do in terms of
business logic!
The next step is to ensure that we have a WishList object on ModelLocator.
This assumes that you have declared the wishList instance alongside the other definitions in
ModelLocator, like so:
public static var wishList : ShoppingCart;
That's really all there is to it. Are you getting a sense of just how rapidly you can add new features to a
Cairngorm application?
</mx:TabNavigator>
Start by looking at the ShoppingCart instance called wishListCartComp in the Tab Navigator
control.
First of all, since the wishListCartComp instance data binds to ModelLocator.wishList,
then any changes to the wish list model are automatically taken care of by the internals of the Shopping
Cart component. That's a tremendous example of component reuse in action!
The addProduct event handler invokes a method, addProductToWishList(), which you need
to define in script in the SideArea.mxml page, as follows:
private function addProductToWishList( event : Object ) : Void
{
EventBroadcaster.getInstance().broadcastEvent(
ShopController.EVENT_ADD_PRODUCT_TO_WISH_LIST, event );
}
This method is repsonsible for translating the user gesture into the appropriate Cairngorm event; now,
when the user either drops a product into the wishListCartComp instance of the shopping cart
component, it is recognised as a user gesture to add something to the Wish List, and this gesture is
translated into the appropriate Cairngorm event.
We re use this addProductToWishList() method in our ProductDetails MXML component, as
shown in this line of code:
addProductToWishList="addProductToWishList( event )"
Clearly, this requires that the ProductDetails MXML component can broadcast out an
addProductToWishList event as the result of a click event happening on a new button "Add to
Wish List" that is placed inside it. So the final piece of view code required is in the following additions
to ProductDetails.mxml.
First, update the definition of MetaData on ProductDetails to advertise the new event you can
broadcast, as shown by the additional highlighted line of code below:
<mx:Metadata>
[Event("addProduct")]
[Event("addProductToWishList")]
</mx:Metadata>
Finally, declare the new button and mutate its click event into a more meaningful business event,
addProductToWishList. Declare a utility method that broadcasts the
addProductToWishList event with some some additional information: the product to be added
(the currently selected item) and the quantity of that product the user has indicated he or she wishes to
add:
private function addProductToWishList() : Void
{
var event : Object = new Object();
event.type = "addProductToWishList";
event.product = selectedItem;
event.quantity = quantity;
dispatchEvent( event );
}
You can then add a new button next to the "Add To Shopping Cart" button, to instead add to the wish
list, as shown in the highlighted code below:
<mx:Button label="Add to Cart" click="addProduct();" />
<mx:Button label="Add to WishList" click="addProductToWishList();" />
This demonstrates one of the best practices we highlighted in the third article in the series: taking a
click event on a button, turning it into a more meaningful event that represents our business domain
("addProductToWishList"), and loosely coupling the MXML components—in this case ProductDetails
and SideArea MXML components through the broadcasting and handling of these events.
And that's it. Now you've added all the code to the MXML that represents the the user experience for
the wish list. By dynamically binding this MXML to the Model Locator class and by translating user
gestures into Cairngorm events, the view is tied into the fabric of the underlying Cairngorm
architecture.
In Perspective
I hope that this final article has sealed a journey of understanding and reinforced the benefits of
building rich Internet applications on the Cairngorm microarchitecture. The benefits demonstrate
themselves very quickly as the application scales in terms of the number of features in the product you
are developing.
Furthermore, I would trust that you now recognise the predictability and consistency in how a
Cairngorm-aware developer chooses to decompose a business requirement into technical tasks that can
be implemented upon the Cairngorm microarchitecture. It is my experience that such consistency and
predictabilty yields so many benefits over and above elegance of code.
On Cairngorm-based projects, it is my finding that developers are able to more accurately and
consistently estimate the cost and effort involved in implementing a particular feature by decomposing
into consistent tasks, and having many previous yardsticks against which to measure.
It is my experience that with a Cairngorm microarchitecture, developers are much more able to
embrace the concept of a "collective ownership," where a developer is no longer a specialist in one
specific area of the application, but is instead a generalist that able to implement feature across the
breadth of an application. Aside from addressing the "what if one of your developers is knocked over
by a bus" dilemma, Adobe Consulting has found that generalization means that the skill level of a team
develops at a much greater rate than if developers become specialists in only a small subset of the
product and the technology platform.
There are many many other benefits to gain from an engineering perspective, but if you are with me
this far, I trust you are already realizing the benefits for yourself. Most importantly, I trust that you have
recognized that standing on the summit of Cairngorm, it's not actually very far down to the bottom.
It's important to qualify that Cairngorm isn't the only way to build rich Internet applications. It is,
however, a proven way of delivering rich Internet applications into production. From that perspective,
it exemplifies best practices from the perspective of the numerous organizations, teams, and individuals
who are working with Flex and Cairngorm together.
If you choose to embrace Cairngorm, or even just the concepts and ideas we have presented through
Cairngorm into your own rich Internet application development, then I am confident your delivery will
be all the more assured because of it.
I look forward to seeing your great work become even greater user experiences.