Top Banner
Developing Flex RIAs with Cairngorm Microarchitecture – Part 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.
75
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Cairngorm

Developing Flex RIAs with Cairngorm Microarchitecture – Part 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.

Page 2: Cairngorm

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

Clarifying the Definition of Frameworks

In software development, the term framework is one of the most overloaded and overused terms within and between development teams. When developers write large pieces of code that they consider significant enough to consider leveraging on other projects, they tend to supplement the code with this term. Thus there are many types of frameworks: persistence frameworks, transaction frameworks, logging frameworks, aspect-oriented frameworks, animation frameworks, unit-testing frameworks, and the like.

Before I delve into the discussion of the Cairngorm framework, it's important that I explain an important distinction that the Adobe Consulting team shares with customers and partners about frameworks—specifically the distinction between application frameworks and architectural frameworks.

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.

, 01/03/-1,
HTML: <NOSCRIPT>
, 01/03/-1,
HTML: </NOSCRIPT>
Page 3: Cairngorm

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.

Applying Design Patterns

It is hard to talk about technical architecture without paying attention to an important movement in software engineering called design patterns.

Without going into detail about software design patterns, let me just say that the expression "there is nothing new under the sun" is never more true than in the discipline of software engineering. Developers often find themselves addressing engineering problems that appear with regular consistency in application development. Almost as consistent as their appearance is the repetition of solutions for these problems. Wherever such recurrences occur, you can identify the solution as a "pattern," indicated by facing the design challenge and finding the appropriate design solution.

The Lure of Design Patterns

Now a warning: When software engineers first encounter design patterns, the realization of a catalog of solutions to their engineering problems can be a powerful one. Often developers recognize a subset of problems they have encountered and then seek to understand other design patterns and where they might apply them. However, the old adage "when all you have is a hammer, everything looks like a nail" can apply here. You can often find "pattern overload" in an application, where developers abdicate responsibility for classes and collaborations and, instead, shoehorn everything into a Factory, Flyweight, Observer, or Decorator.

Used appropriately, however, design patterns can be a powerful tool in a developer's toolbox. Design patterns not only offer common solutions to problems, but the way developers apply them to an application indicates the intention of the implementation. For instance, whenever you see a Singleton in a code base, you understand that this is a class for which there should be only one instance. Likewise, whenever you come across a Factory, you recognize that there are a number of different objects that a manufacturing class can construct.

Rising through the technical architecture—from the ground-zero of detailed implementation towards the helicopter view of high-level system design—you can begin to appreciate the design in other, recurring ways. Like the Mandelbrot, you begin to recognize higher order structure as your lower level structure collaborates. Thus design patterns begin to coalesce in repeatable ways, offering higher level solutions to higher level design problems.

Microarchitecture as a Composition of Design Patterns

Although a design pattern might have offered a specific solution to a specific problem (in this case, using the Singleton pattern for the problem of ensuring that only one instance of a class ever exists), when a collection of patterns regularly collaborate with one other, their assembly is for the greater good

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 4: Cairngorm

of a greater aim: "How do I intercept user gestures and ensure that a worker class assumes responsibility?" or "How do I centralize the business logic that might be used in more than one context on the client, even though it is actually implemented as a collection of service calls to a number of different types of services on a collection of servers?"

When you start to assemble collections of design patterns into these higher order but nonetheless highly generic systems, you communicate these as "microarchitectures."

Let's take this high-level discussion of application frameworks, architectural frameworks, design patterns, and microarchitectures and put it into context. When Adobe Consulting speaks about the Cairngorm framework, we are speaking about an architectural framework—a starting point for technical architecture that is generic enough to apply in most cases to medium-to-complex Flex RIAs.

Furthermore, when we speak about the Cairngorm framework, we do not mean some monolithic architecture that limits your freedom as a developer to solve problems. Rather, when we advocate the Cairngorm framework, we mean the following:

A lightweight framework that offers a little prescription for some of the challenges consistent with the Flex RIAs we have encountered

Using a small number of relevant design patterns, where the moving whole is ever so slightly greater than the sum of its static parts

A microarchitecture for RIA development—a starting point for your technical architecture that solves the problems as they have been solved successfully before

If we can see further, it is because we are standing on the shoulders of giants.

A Short History of Cairngorm

iteration::two, a software consultancy that Alistair McLeod and I cofounded, recognized that many of the design challenges faced and successfully solved in the world of J2EE application development were still relevant issues in the world of RIA development. We came to this realization back in the days of RIA development with Flash, Flash Remoting, and J2EE—a period of RIA history that we'll look back upon with the same fondness and nostalgia as a C++ programmer has for 6809 assembly language.

Borrowing a subset of a collection of design patterns advocated by Sun Microsystems as the Core J2EE Pattern Catalog, we first presented these patterns to the Flash community in our book, Reality J2EE: Architecting for Macromedia Flash MX (Pearson Education, 2003). To accompany the release of Flash MX 2004, we presented the patterns in a chapter called "ActionScript 2.0 Design Patterns for RIA Development" in Macromedia Flash MX 2004 ActionScript 2.0 Dictionary (Macromedia Press, 2003).

As the RIA technology platform matured from the design-centric approach of Flash towards the declarative programming model of Flex, most of the motives for applying these patterns remained. However, the Flex programming model offered us ever more elegant ways to implement these patterns. Furthermore, some patterns that we considered hugely valuable to Flash RIA developers (such as the ViewHelper pattern) became less relevant in the Flex RIA world, allowing us instead to advocate new Flex-specific patterns of our own, such as the ModelLocator pattern.

At MAX 2004 we announced our decision to release the Cairngorm framework as an open-source project for Flex; a decision that has led very much to its widespread adoption within the community.

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 5: Cairngorm

What Cairngorm Teaches You

Cairngorm is a microarchitecture that addresses three key areas that we have always tried to provide as best-practice recommendations for our customers and partners:

Handling user gestures on the client Encapsulating business logic and server interactions Managing state on the client and representing this state to the user interface

Cairngorm offers a microarchitecture (collection of design patterns) that aids us in consistently solving these recurring design challenges.

As you read this series of articles, you will learn the following:

How the Front Controller and Command patterns implement a "Service to Worker" microarchitecture for listening and responding to user requests.

How the Business Delegate and Service Locator patterns interact in such a way that you can reuse the business logic and encapsulate it so that it establishes a clear contract between client and server-side development teams, independent of server-side technical implementations such as web services, Enterprise JavaBeans, ColdFusion components, or even RESTful architectures using XML over HTTP.

How the Value Object pattern from J2EE can collaborate with the Model Locator pattern first implemented in Cairngorm 0.99 to be an elegant strategy for maintaining a stateful client with a rich and cinematic user experience.

Current State of Cairngorm

Cairngorm is currently in version 0.99. It never made it to a 1.0 release because iteration::two became immersed in a trail of acquisitions, first by Macromedia and then by Adobe Systems. Furthermore, with Flex moving towards a 2.0 release of its own, the introduction of ActionScript 3.0, a number of improvements to the underlying Flex application framework, an exciting array of new services (including Flex Enterprise Services), and the evolution of the Cairngorm Committee—now comprised of a committed core of Adobe consultants and Adobe engineers, as well as numerous community members behind it—Adobe Consulting is focused on aligning a comprehensive Cairngorm 2.0 release that leverages the underlying Flex 2.0 framework.

The core Cairngorm concepts remain the same, however. Only the underlying implementation changes due to additional thought leadership and best practices on how to leverage Flex 2.0 features such as Flex Data Services and Flex RPC Services within a Cairngorm application. For those working with Flex 2.0 in Labs, we will also make available regular alpha releases of Cairngorm 2.0.

Note: In the final article of this series, I explore the criteria you can follow to decide when to use Cairngorm—and when not to use it. We do not advocate for a second that Cairngorm is the only way to build Rich Internet Applications. Nor do we even suggest that our best practices are the only best practices. In fact, others may promote best practices that seem to contradict what Cairngorm suggests.

All we ask is that you first try to understand the problems that Cairngorm tries to solve before preemptively solving them with Cairngorm. If you are learning Flex for the first time and consequentially building one of your first Rich Internet Applications, I strongly suggest that you become comfortable with the myriad of new tools and techniques that the technology and platform offer you before complicating your learning curve with Cairngorm.

This is not to say that Cairngorm is complex. On the contrary, it is important that you be confident and comfortable building simple RIAs that do not require the benefits of a technical architecture such as

Page 6: Cairngorm

Cairngorm before taking advantage of the benefits that Cairngorm prescribes.

Parts 2–6 will ensure that both seasoned and novice Flex developers understand the RIA challenges that created the problems for Cairngorm to solve. We want to explain as clearly as we can, and with detailed code-level examples, the elegant way in which Cairngorm can help you solve them.

Where to Go from Here

The aim of this article was to introduce you to the Cairngorm framework by clarifying the ambiguity around the definition and usage of frameworks, why you might use a framework, and the origins and the future of the Cairngorm framework.

Cairngorm is an architectural framework that provides a suggested implementation of a technical architecture upon which you can build your own, more complex, application-specific architectures.

By basing your application on the Cairngorm architecture, however, there are some generic and fundamental design challenges that you won't have to solve by yourself while leveraging the benefits inside Flex, such as how to handle user gestures in an elegant fashion, how to handle server interaction and business logic in an elegant and scalable fashion, and how best to manage state on a rich and immersive client application.

I gave a brief overview of the engineering discipline of software design patterns and how iteration::two borrowed those that were useful to us as J2EE developers, and which are still useful for our RIA development. Having an aerial view of the technical architecture usually helps developers identify recurring collaborations of design patterns. The concept that such collaborations, known as microarchitectures, exist is the foundation of the Cairngorm framework.

When consulting, I often say that the difference between theory and practice is that, in theory, there's no difference between theory and practice. In Part 2, I depart from the discussion of context and background of design patterns and microarchitecture, and instead present Cairngorm in terms of providing four key benefits:

Maintaining state on the client Architecting the user experience Implementing feature-driven development Invoking server-side services

In Part 2, you'll get a hands-on feel for the ways we leverage the Cairngorm framework in application development, the problems that Cairngorm solves, and the ease with which you can consistently deliver highly scalable, maintainable, and robust Rich Internet Applications with Flex.

Developing Flex RIAs with Cairngorm Microarchitecture – Part 2: Keeping State on the Client

Developing Flex RIAs with Cairngorm Microarchitecture – Part 2: Keeping State on the Client

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 7: Cairngorm

Steven Webster

Adobe Consultingwww.richinternetapps.com

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

 

Cairngorm version .99

Download Cairngorm version .99

Page 8: Cairngorm

Read Developing Flex RIAs with Cairngorm Microarchitecture – Part 1: Introducing Cairngorm before reading Part 2.

Introducing the Cairngorm Store

The Flex Store application showcases the features of the Flex application framework. It demonstrates how to use various layout containers, navigator containers, controls, effects, data binding, the Drag-and-Drop manager, form validation, and the History manager. Furthermore, Flex Store articulates the component-development model of Flex, showing how to create loosely coupled components that integrate through an event-driven architecture. The Flex Store application is an excellent way to become familiar with declarative layout using MXML and business logic development using ActionScript.

With the Cairngorm Store (see Figure 1), I take this familiar application and completely rebuild it as an enterprise rich Internet application—an application that is rapid, scalable, and maintainable—through Cairngorm.

Figure 1. The Cairngorm Store

The Cairngorm Store borders on the complexity of a rich Internet application that would benefit from a Cairngorm architecture. Indeed in Part 3, I 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.

The Cairngorm Store: Four Key Challenges

Instead of an academic discussion of design patterns we selected for Cairngorm, I will explain the common high-level challenges that often occur when developing rich Internet applications and how to

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 9: Cairngorm

achieve these solutions with a Cairngorm development process. By describing the development process, I also explain the various constituent design patterns used in the Cairngorm framework.

A good technical architect first sees application development as a solution to a business problem, then as a system that realizes this solution, then as a technical architecture for the custom software in the system, and finally as the detailed class-level implementation of that technical architecture.

The approach taken here in Cairngorm and Cairngorm Store 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 faces, whether building mortgage calculators, retirement planners, online banks, single-screen checkouts, full e-commerce applications, or interactive maps. These challenges are as follows:

Keeping state on the client Architecting the view Driving feature-driven development Invoking server-side services

The challenges and requirements for the Cairngorm Store are no different.

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 the user must fill in to fulfil his or her order. You must architect the view.

There are a number of features that users demand in the application. The users must be able to do the following:

View all products and get detailed information for any selected product Add products to, or remove products from, their shopping cart Purchase products in their cart Proceed to and complete a checkout process.

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 or a system such as SAP. 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 studying it against the four key challenges.

In this article, I take a look at the first of these four key challenges: keeping state on the client.

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 10: Cairngorm

Keeping State on the Client

One key shift in mindset when you move from the development of web applications to rich Internet applications is that the client is stateful.

Web application developers are all too familiar with the stateless request/response approach. Therefore, they have created various approaches for maintaining state on the client. Whether you continually stuff data into an HTTP request as the client and the server exchange pages, or you use URL encoding or cookies to keep some limited state information on the client, or you take advantage of abstractions of these features such as J2EE sessions, the typical application developer's is to maintain state on the server, and make it available to the client.

With RIAs however, web application developers can break free from the shackles of HTTP request and response. No longer do you 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, for instance, you'll shrug your shoulders right now because all of this will sound all too familiar—it is.

Difference Between State and the Model in MVC

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 some further 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 advocate best practice object-oriented development. Indeed, that you accept the benefits and practice the principles of OO are almost prerequisites to understanding this article.

However, I 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? I mean 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."

Synchronizing Client and Server State

One challenge is relatively unique to the stateful client of a rich Internet application: The state on the client usually reflects the state held on the other side of the wire, in the server middleware. Let's examine this design problem some more.

In server-side application development, one key challenge is that state often sits within two tiers of an application architecture: 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. Many solutions address some of the challenges of keeping state consistent across the two tiers. 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

Page 11: Cairngorm

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 (H8), 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 these tiers. As soon as you keep the same information in two places, there is a risk that changes to it in one place are not reflected in another place, or that changes in one place overwrite changes in another place. Suddenly, the developer must care about concurrency, locking, transactions, rollback, and other approaches for ensuring that the model is consistent in these different places in the architecture.

As RIA developers, you are abstracted from the complexities between the business and integration tier; your concerns focus more on how to ensure that the model in the business tier is consistent with the model in the presentation tier.

You can achieve this most easily by ensuring that you have a consistent object model on the client and server, and that you minimize the effort required to keep these object models synchronized.

Introducing the Value Object / Data Transfer Object Pattern

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 our 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 value objects play a dual role in a rich Internet application, however. In addition to keeping the value of an item on the client, they are useful structures with which you can exchange or transfer 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 a 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 to calling the Value Object pattern the Data Transfer Object pattern instead.

We conceived Cairngorm before the term data transfer object (DTO) became popular, so I call the business objects value objects or VOs.

For example, look at the VOs in the Cairngorm Store application, which reside in the org/nevis/cairngorm/samples/store/vo package:

class org.nevis.cairngorm.samples.store.vo.ProductVO implements ValueObject, Comparable { public static var registered:Boolean = Object.registerClass( "org.nevis.cairngorm.samples.store.vo.ProductVO", ProductVO ); public var id : Number; public var name : String;

Page 12: Cairngorm

public var description : String; public var price : Number; public var image : String; public var thumbnail : String;}

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?

Keeping a Consistent Object Model Between Flex and Java

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 Flex server about the relationship between the classes. In Cairngorm, you achieve this by advocating that each and every instance of a value object class contains the following:

public static var registered:Boolean = Object.registerClass( "org.nevis.cairngorm.samples.store.vo.ProductVO", ProductVO );

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.

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 13: Cairngorm

Using XDoclet2 with ActionScript

XDoclet2 is a java-driven ActionScript code generator. Joe Berkowitz of Allurent created a labor-saving, open-source project for creating 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 www.allurent.com/joeb/xdoclet2.

To recap, value objects ensure that you do the following:

Adhere to the best practice of representing client-side data with a meaningful object model Maintain a consistent object model on the client and server Leverage the capabilities of the Flex server to translate value objects between Java and

ActionScript as they "cross the wire" between client and server

Getting data to the client that is consistent with the server is only the first part of the problem. What do you do with the client-side data and where you keep it to enable you to accomplish this task?

Binding the Model and View Together

One of the primary reasons for wanting 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.

One of the unsung features of the Flex application framework is the data binding capabilities of Flex. Data binding allows two objects to act as a source and 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 witnessed development teams convolute solutions for updating the user interface whenever the underlying model changed. Furthermore, these teams became tangled in a mess pretty quickly when displaying data in different parts of the view in different ways. Let's say 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 update data in a dashboard summary about key assumptions for a business. 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 parts of the application development (in his parts of the code base). Consequently, when another developer needed to use that data in another part of the application, she would have 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 to change—adding or removing any components (storeView, textualList) would require that a developer update any references to productGrid.

Additional strategies include passing data up and down through MXML component instantiations. Consider the following graphical representation of MXML components, where data is in a leaf node of

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 14: Cairngorm

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 incredibly brittle to changes in the implementation of the view. It leads to regular errors because it incorrectly passes data down the chain somewhere. 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.

Enter the Model Locator Pattern

Watching developers fall repeatedly into these traps, the Adobe Consulting team conceived the Model Locator pattern as a best practice for Flex developers to adopt. The Model Locator pattern is unique because it is not a pattern we borrowed from the Core J2EE Pattern catalog. Instead, we created this pattern particularly for Flex application development. Our motivation was to have a single place where the application state is held in a Flex application and where view components are able to "locate" the client-side model that they wish to render.

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 single instance of the ModelLocator class. In this way, whenever the model is updated in ModelLocator, 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, and in the Cairngorm Store we have a single class that implements this marker interface, org.nevis.cairngorm.samples.store.model.ModelLocator.

As you take this opportunity to review the source for this ModelLocator class, let me point out a few examples of its usage. In the Cairngorm Store, there is always a notion of the "currently selected product." 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 panel, and uses the property to decide what to add to the shopping cart when the user presses the Add to Cart button.

See the ModelLocator source in the following entry:

public static var selectedItem : ProductVO;

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

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 15: Cairngorm

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;

//------------------------------------------------------------

public static var VIEWING_PRODUCTS_IN_THUMBNAILS : Number = 1;public static var VIEWING_PRODUCTS_IN_GRID : Number = 2;public static var VIEWING_CHECKOUT : Number = 3;

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; private function onCreationComplete() : Void { ModelLocator.initialise(); }

]]></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"

Page 16: Cairngorm

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.

Where to Go from Here

In this article, I identified a number of challenges that are consistent across rich Internet applications, regardless of the business domain of the application. I'll use these challenges to explore the Cairngorm development model in greater detail.

Specifically, I introduced the first of these challenges: keeping state on the client. I also observed how 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 help you keep data on the client as the user uses the application. Now you assume a certain degree of responsibility for that client-side state.

With 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. However, you recognize that wherever an object model already exists on the client, you can take advantage of the Flex server 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 first 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.

Therefore, I presented the Value Object (VO) pattern in Cairngorm, also referred to in the software engineering community as a Data Transfer Object (DTO) pattern.

Having a sensible way of representing the client-side object model guarantees that developers will be sensible about where they keep that state on the client, how they share the client-side state between different components (who may have different ways of rendering the state), or how they ensure that the view is always showing the most up-to-date rendering of the state.

Consequently, I presented the Model Locator pattern, first introduced in Cairngorm, as a strategy for ensuring that state is held in a singleton class that can be located from anywhere in your Flex application architecture. The singleton nature of the Model Locator pattern ensures that you don't suffer from duplicate state on the client, gives a large development team a consistent place to expose and share application-level state as instances of value objects, while ensuring that developers leverage the powerful data-binding capabilities of Flex to ensure that the view is notified when the model has changed.

In Part 3, I'll focus your attention a little more on the view. I'll explore in greater depth how to leverage Flex data binding with state stored on ModelLocator before exploring more of the Cairngorm framework.

Developing Flex RIAs with Cairngorm Microarchitecture – Part 3: Architecting the View

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 17: Cairngorm

Developing Flex RIAs with Cairngorm Microarchitecture – Part 3: Architecting the View

Steven Webster

Adobe Consultingwww.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

Page 18: Cairngorm

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

About the author

Steven Webster is the practice director for Rich Internet Applications at Adobe. Previously, Steven was the technical director at iteration::two, a world-leading Rich Internet Application consultancy based in Edinburgh, Scotland. Steven is the author of Reality J2EE: Architecting for Flash MX and coauthored ActionScript 2.0 Design Patterns for Rich Internet Applications (ActionScript 2.0 Dictionary) and Developing Rich Clients with Macromedia Flex with Alistair McLeod. Steven speaks regularly at conferences and user group meetings on technical and business aspects of RIAs. Steven is the core contributor to the open-source Cairngorm project, a microarchitecture for RIAs based on J2EE patterns which was innovated by iteration::two over a number of Flash and Flex RIA developments.

Architecting the View

Perhaps the first and most important thing to understand about architecting your view logic with Cairngorm is that Cairngorm doesn't actually care! One key value of the Cairngorm architecture is that it is lightweight and places little imposition on the Flex developer, so as not to stifle individual technical flair.

Rather, Cairngorm offers clear integration points between the "rich" and the "application" in RIA.

The user experience must represent the application's current state by displaying particular screens and rendering the model in a specific way. This might mean showing the products in Cairngorm Store as graphic thumbnails instead of tabular data, deciding which subset of the products to make visible based on a slider that specifies a price range that the user selects for the products, or recognizing that the user has finished shopping and guiding them to complete the checkout process. You make decisions on the dynamically rendered views by integrating the view with the model. I've already discussed Value Objects and the Model Locator pattern in detail, but I will complete this part of the puzzle shortly.

A rich Internet application is a communication between the user and the application. The best form of that communication is bidirectional. While the user experience must "speak" to the user by rendering the view according to the state of the model, the user experience needs to "listen," hearing what the user wants to do with the application and responding accordingly. In this case, user requests are called

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 19: Cairngorm

gestures; user requests gesture to the application what the user wants to do as he or she clicks buttons, drags objects, completes and submits forms, and so forth. Responding to user gestures (by listening to the user), doing something on behalf of the user, and then "speaking" back to the user by updating the view (or, more correctly, updating the model, which you might now recognize automatically updates the view) becomes the flow of conversation between user and RIA. Elsewhere in this article, I'll explain the Cairngorm design patterns and architecture that can mediate the conversation between user and RIA.

Moving on, the structure of the article is as follows. I'll complete the discussion of the Model Locator pattern by explaining how you build the view to take advantage of data binding in the Cairngorm Store.

Next, even though Cairngorm does not prescribe the view, I'll offer some advice on best practices for structuring your view. You can use much of this advice whether or not you're using Cairngorm. However, if you are using Cairngorm, heeding this advice will help you make architectural decisions about your MXML components.

Finally, I'll introduce you to a "microarchitecture within a microarchitecture," called the Service to Worker concept. Service to Worker is a collaboration of design patterns that the Adobe Consulting team first advocated within Cairngorm as a way to mediate the essential conversation between the user and the application on a feature-by-feature basis.

Binding the Model to the View

By now, you have an understanding of the Model Locator as a singleton that you can use to store and retrieve all states in your rich Internet application. Furthermore, and as much as is appropriate, your application holds state as Value Objects (VOs) and collections of Value Objects, rather than as primitive types (strings, numbers, and Booleans), which rarely communicate the same information to fellow application developers.

Now I'll take a detailed look at the Model Locator strategy and the challenges it can face, and how you can leverage the powerful data binding capabilities of the Flex framework as you collaborate with the Model Locator and Value Object patterns.

The Product Details panel in the Cairngorm Store (see Figure 1) shows the detailed product information for the product that the user last selected by clicking a product in the graphic or text product view.

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 20: Cairngorm

Figure 1. Detail view of a product

The entry point for the Cairngorm Store is the Main.mxml file, which partitions the design into two components:

BodyPanel: Panel that represents the main body of the application and renders the graphical and textual product views, and the checkout process

SideArea: Vertical box container that holds the ProductDetails and ShoppingCart components

Look at the instantiation of the SideArea component. As in Part 2, this example uses data binding to bind the details that the ProductDetails component renders to the selectedItem instance of a ProductVO value object held in ModelLocator:

<details:ProductDetails id="productDetailsComp" width="100%" height="325" currencyFormatter="{ ModelLocator.currencyFormatter }" selectedItem="{ ModelLocator.selectedItem }" addProduct="addProductToShoppingCart( event )" />

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[[

// set during MXML instantiation public var selectedItem : ProductVO;

]]></mx:Script>

<mx:HBox>

<mx:Canvas width="150" height="140" clipContent="false">

<mx:Image

Page 21: Cairngorm

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 }" />

Page 22: Cairngorm

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.ProductThumbnail" 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.

Best Practices for MXML Development

The preceding discussion touches on best practices that Adobe Consulting urges developers to adopt when implementing their own user experiences with MXML.

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 23: Cairngorm

One key best practice is "to develop with components in mind." With MXML, it is incredibly easy to create components that extend base tags with application-specific functionality. If you have ever had to review someone else's MXML code and tried to trace your way through hierarchies of VBox controls containing HBox controls containing VBox controls containing TileList controls containing VBox controls, and so forth, you will quickly realize the value in creating components that have semantic meaning.

If you review the Cairngorm Store's code, you will quickly be able to cross-reference MXML components with their visual renderings on the screen. By creating components such as Main, SideArea, ProductDetails, ShoppingCart, ProductDetails, ProductThumbnail, GraphicalProductList, TextualProductList, ProductAndCheckoutViewStack, and so forth, you will be able to navigate your own source code more easily, never mind someone else's code.

When designing with components in mind, learn to use the events infrastructure within Flex to couple your components loosely. For instance, create your own events, which describe what happens at the user level, not at the Flex framework level. Consider the difference between a button within a VBox control having some code within a click="" event handler, versus a component called ShoppingCart that responds to an event called productAdded. When you create your own events, broadcast these events from within your components and handle these events accordingly. Your code base will become much more maintainable.

Again, consider the ProductDetails component in the Cairngorm Store, which is an application-specific rendering of a Panel component that broadcasts an event whenever the user clicks the Add to Cart button:

<mx:Panel xmlns:mx="http://www.macromedia.com/2003/mxml" xmlns:details="org.nevis.cairngorm.samples.store.view.productdetails.*" title="Product Details" styleName="productDetails">

<mx:Metadata> [Event("addProduct")] </mx:Metadata>

<mx:Script> <![CDATA[

private function addProduct() : Void { var event : Object = new Object(); event.type = "addProduct"; event.product = selectedItem; event.quantity = quantity; dispatchEvent( event ); }

: : : <mx:Button label="Add to Cart" click="addProduct();" />

</mx:Panel>

Page 24: Cairngorm

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.

Where to Go from Here

This article focused on understanding how to construct a view using the building blocks of MXML and ActionScript. More importantly, I showed how to build this view on the underlying architecture of the Cairngorm skeleton.

By now you understand how to manage client-side state using the Model Locator and Value Object patterns. Furthermore, although I've given some guidance and best practices to consider when architecting your view, such as the MXML and ActionScript classes that describe the user experience, Cairngorm makes no assumptions and imposes no restrictions on how you choose to architect your view.

I've discussed how to implement the view and keep state on the view. In Part 4 I'll discuss how to implement features with the addition of business logic within commands—the "application" in rich Internet application.

Developing Flex RIAs with Cairngorm Microarchitecture – Part 4: Feature-Driven Development

Steven Webster

Adobe Consultingwww.richinternetapps.com

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 25: Cairngorm

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

Page 26: Cairngorm

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 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.

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 27: Cairngorm

The Service to Worker Microarchitecture

In this article, I introduce the most fundamentally important patterns in a Cairngorm application: the Front Controller, Event Broadcaster, and the Command pattern.

Rich Internet applications have grown in complexity due to the number of features required in the application. Consider the following features in the Cairngorm Store:

Get products from database Sort products by name or price Filter products according to price slider Add product to shopping cart Remove product from shopping cart Check out an order

There are six features, requirements, use cases, or stories, depending on the methodology you follow for capturing business requirements from a client. For a real-world rich Internet application such as the Cairngorm Store, the number of these features easily increases from 5–10 requirements to 50–100 different features. The Adobe Consulting group has consulted on and developed applications that have hundreds to thousands of these features, delivered by teams of 20–30 developers.

As the number of features grows, so too does the scale and complexity of the rich Internet application. It is in this case that Cairngorm can help you scale and simplify the complexity.

Service to Worker: Mapping User Gestures and System Occurrences to Events

The user executes the vast majority of features through user gestures. Remember that a user gestures the application through mouse or keyboard; for instance, through clicking a button, double-clicking an entry in a data grid, dragging and dropping an object, or submitting a form.

Consider the Cairngorm Store features listed previously. All but the request to get products from the database occur because of a user gesture. Here are some examples of user gestures:

User sorts products by clicking a button User drags a slider component to filter products User adds products to the shopping cart by clicking an Add to Cart button or by dragging and

dropping a thumbnail or product image into the shopping cart

More often than not, you map the execution of a feature to a user gesture.

If you come from a web application development world, you will recognize that each feature requires an HTTP request to the server, with the server doing some of the work on your behalf and returning data to the client with a browser refresh.

It is not always the case with an RIA that a feature "execute" follows a user gesture; the fetching of the products from the database is something done immediately when the application starts. Rather than resulting from a user gesture, getting the products from the database is an event that happens within the system. Other such system-level events might include a feature that executes periodically, such as fetching e-mail messages every 30 seconds.

The single biggest innovation is that the Cairngorm framework treats user gestures and system-level events the same way by mapping them to a Cairngorm event. Breaking free of the request/response paradigm of HTML-based technologies, user requests are no longer synonymous with HTTP requests.

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 28: Cairngorm

Instead, user requests are internal events that components can broadcast when they recognize a user gesture or system event. Rich Internet applications handle user requests without a server round trip.

Service to Worker: Listening for Events with the Front Controller

Consider the events in the Cairngorm Store. The features mentioned previously map to events such as getProducts, addProductToShoppingCart, deleteProductFromShoppingCart, filterProducts, sortProducts, and so forth. Whenever a user gesture indicates the desire to execute a feature, Cairngorm necessitates that you broadcast an appropriate event.

In response to each event, I have mentioned executing a feature. A common design pattern from the original Gang of Four pattern catalog is an excellent choice in such a scenario. In this pattern, you implement features as classes, called commands. Each and every command exposes a single entry point, a method called execute(), which allows a third party to invoke the command without understanding what the command accomplishes. Often these commands are called worker classes because they are responsible for actually carrying out the work behind the application.

Understanding Command Classes

Typically within Cairngorm, adding a new feature to the application is considered adding a new Command class that can accomplish the work associated with the feature.

Consider one of the key features of Cairngorm Store—the ability to add products to a shopping cart. To implement this feature, you create a new Command class called AddProductToshoppingCartCommand, the full code of which follows:

AddProductToshoppingCartCommand, the full code of which follows:import org.nevis.cairngorm.commands.Command;import org.nevis.cairngorm.control.Event;import org.nevis.cairngorm.samples.store.model.ModelLocator;import org.nevis.cairngorm.samples.store.vo.ProductVO;

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

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 29: 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.

Managing Commands with a Controller

In the real world, having a large collection of workers with no one in charge is a recipe for chaos. Delegating responsibility to someone to take charge and control the workers from the front ensures that workers do their duty when they're required to do so.

Borrowing a design pattern from the J2EE community, but implementing it to respond to events on the client rather than HTTP requests on the server, Cairngorm introduces the Front Controller pattern as a single point of entry for all Cairngorm events.

Let's take a look at the Front Controller pattern in the Cairngorm Store:

class org.nevis.cairngorm.samples.store.control.ShopController extends FrontController{ public function ShopController() { initialiseCommands(); }

Page 30: Cairngorm

//----------------------------------------------------------------------------

public function initialiseCommands() { addCommand( ShopController.EVENT_GET_PRODUCTS, new GetProductsCommand() ); addCommand( ShopController.EVENT_ADD_PRODUCT_TO_SHOPPING_CART, new AddProductToShoppingCartCommand() ); addCommand( ShopController.EVENT_DELETE_PRODUCT_FROM_SHOPPING_CART, new DeleteProductFromShoppingCartCommand() ); addCommand( ShopController.EVENT_FILTER_PRODUCTS, new FilterProductsCommand() ); addCommand( ShopController.EVENT_SORT_PRODUCTS, new SortProductsCommand() ); addCommand( ShopController.EVENT_VALIDATE_ORDER, new ValidateOrderCommand() ); addCommand( ShopController.EVENT_VALIDATE_CREDIT_CARD, new ValidateCreditCardCommand() ); addCommand( ShopController.EVENT_COMPLETE_PURCHASE, new CompletePurchaseCommand() ); } //-------------------------------------------------------------------------

public static var EVENT_GET_PRODUCTS = "getProducts"; public static var EVENT_ADD_PRODUCT_TO_SHOPPING_CART = "addProductToShoppingCart"; public static var EVENT_DELETE_PRODUCT_FROM_SHOPPING_CART = "deleteProductFromShoppingCart"; public static var EVENT_FILTER_PRODUCTS = "filterProducts"; public static var EVENT_SORT_PRODUCTS = "sortProducts"; public static var EVENT_VALIDATE_ORDER = "validateOrder"; public static var EVENT_VALIDATE_CREDIT_CARD = "validateCreditCard"; public static var EVENT_COMPLETE_PURCHASE = "completePurchase"; }

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,

Page 31: Cairngorm

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.

Service to Worker: Broadcasting Cairngorm Events with the Event Broadcaster

The final piece of the puzzle involves figuring out how to broadcast the events to which the controller responds.

Cairngorm provides a singleton EventBroadcaster class that collaborates with the FrontController class. Whenever you want to dispatch Cairngorm events, dispatch them with the Cairngorm Event Broadcaster.

Take a look at the different ways a user might perform user gestures to add products to a shopping cart.

The user might click the Add to Cart button on the ProductDetails MXML component. If you look at the code for ProductDetails.mxml, notice that you have a private helper method on the MXML that broadcasts an addProduct event out of the component to the parent component, SideArea.mxml:

private function addProduct() : Void{ var event : Object = new Object(); event.type = "addProduct"; event.product = selectedItem; event.quantity = quantity; dispatchEvent( event );}

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_

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 32: Cairngorm

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.

Dragging and Dropping Products into the Shopping Cart

It's trivial to implement a drag-and-drop feature to your application. The ShoppingCart component (ShoppingCart.mxml) has a drop handler called doDragDrop() that behaves exactly as the button handler did previously, broadcasting an addProduct event from the ShoppingCart component, which is handled in the SideArea MXML component, which is identical to the ProductDetails component:

<cart:ShoppingCart id="shoppingCartComp" width="100%" height="100%" shoppingCart="{ ModelLocator.shoppingCart }" select="ModelLocator.selectedItem = event.target.selectedItem" currencyFormatter="{ ModelLocator.currencyFormatter }" addProduct="addProductToShoppingCart( event );" deleteProduct="deleteProductFromShoppingCart( event );" gotoCheckout="ModelLocator.workflowState = ModelLocator.VIEWING_CHECKOUT;" />

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.

Page 33: Cairngorm

Where to Go from Here

We've flown quite a distance across the terrain of a Cairngorm application now. Regardless of how you choose to build rich and immersive user experiences using MXML and ActionScript, Cairngorm plays an important role in listening to the user's interaction with these experiences, in translating these interactions into an understanding of what the user expects, and in executing work on the user's behalf.

In Cairngorm, you have learned to achieve this goal with a clean collaboration of design patterns: Front Controller, Event Broadcaster, and Command. These patterns form a tight microarchitecture called Service to Worker.

Every time you add a feature to the application, you do so by creating a specific Command pattern, such as AddProductToShoppingCartCommand. This command performs the work associated with the user gesture or system event that caused the command to execute. For Cairngorm to execute this command, you use the addCommand() method in FrontController to register each Command pattern with an appropriate event name. Furthermore, I've advocated a best practice of naming your events as static constants on your Front Controller pattern so that you can benefit from compile-time checking of your event broadcasting, rather than silent runtime failure if you mistype event names.

I've shown you how Cairngorm provides a base class for your Front Controller patterns and an interface for your Command patterns. If you extend and implement these classes, you can use the Event Broadcaster pattern from anywhere in your application to translate user gestures or system-level events into events that cause Cairngorm to pass control to the appropriate Command pattern.

This initial foray into application development may cause you to pause and reflect on the flow of control between different parts of the application architecture. You will quickly become comfortable with this flow, however, and you and your team will approach the addition of each and every feature to a Cairngorm application in the same way. In short, you will add an event to the controller, register it with a Command class, and implement the Command class to do all the work. As you drive the development of your application with feature after feature, you will do so in a consistent, predictable manner that scales well.

No more scattering of business logic across an ever-growing ecosystem of MXML components. No more monolithic classes that state "if the application just did this, then do that; but if it did this, then do the next thing..."

The scalability of design, maintainability of the code base, and the simple, consistent approach to delivering new features into the application bring out the major benefits of using the Cairngorm microarchitecture.

I've discussed how to implement the view and how to keep state on the view, or the rich in rich Internet application. I've discussed how to implement features with the addition of business logic encapsulated in commands, or the application in rich Internet application.

In Part 5, I'll discuss the Internet in rich Internet application. You'll learn how to complement the richness of the user experience with the ability to reach out and invoke business logic that exists on a server—whether it's your own application server or third-party services exposed on third-party servers. This final piece of the puzzle explains everything you need to know to design, architect, and deliver your own highly complex enterprise rich Internet applications.

Developing Flex RIAs with Cairngorm Microarchitecture – Part 5: Server-Side Integration

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 34: Cairngorm

Developing Flex RIAs with Cairngorm Microarchitecture – Part 5: Server-Side Integration

Steven Webster

Adobe Consultingwww.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.

Page 35: Cairngorm

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 5.

Integrating with Flex RPC Services

One of the challenges when developing a rich Internet application are which tradeoffs to make with regard to which functionality the application should and could execute on the client, and which functionality should reside on a server. The decisions may take into account issues such as performance, scalability, and security or may simply be dictated by the desire to provide a rich user experience upon an existing investment in server-side infrastructure.

One luxury of a rich Internet application is the seamless means by which server integration now occurs. With a web application, requests to the server for data typically results in a page refresh for the user. With an RIA, however, the user is oblivious to either client- or server-side processing. Requests for data do not interrupt flow of control; data requests are made asynchronously and the application is notified when these results become available.

There are a number of mechanisms available for integrating an RIA with server-side data. These are now known as Flex remote procedure call (RPC) services:

HTTPService: Passes textual data over HTTP. This is often used to fetch RSS feeds, read XML generated by a server, or simply pass text-based data from server to client.

WebService: Allows a Flex application to invoke server-side methods using SOAP web services and handle the results of web service calls. These web services may reside on the server that has served the rich Internet application or may call third-party web services through a proxy on the Flex server.

RemoteObject: Allows a Flex application to directly invoke methods on Java classes residing on the application server that has served the RIA. Data transfer is in a binary format (AMF) over HTTP or HTTPS, with the Flex server translating Java and ActionScript objects as they pass over the wire.

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 36: Cairngorm

I am not going to explore these different services in any detail, or offer any thoughts on best practices for client-server integration. There is a significant amount of discussion of these RPC services in the official Flex documentation, as well as a great deal of discussion as to why you might choose one method over another in my book, Developing Rich Clients with Macromedia Flex.

Flex provides you with three MXML tags—HTTPService, WebService, and RemoteObject—all of which do a great amount of heavy lifting for application developers. Using these tags, you can easily specify a service location (URL, WSDL location, or Java class), invoke methods on that service, pass data in these method invocations, and receive the results in the future in a result handler.

As your application scales, it is likely that the number of services you wish to call will increase. It is also likely that you'll call a mixture of HTTP services, web services, and methods on Java classes. Likewise, it is also likely that different developers will have different uses for many of the same services. To solve this recurring challenge in Flex application development, the Cairngorm framework offers a pair of design patterns—once again borrowed from the J2EE community—to introduce some consistency where services are declared, how they are invoked, and how their results are handled within your RIA.

Locating Services with the Service Locator

Often, one challenge large development teams often face is when all team members build against the same server-side infrastructure. For example, a developer who develops a component realizes he needs to make a call to a Java class, and deep within the bowels of his component, places a RemoteObject tag pointing at the Java class. Meanwhile, three other developers on the team do exactly the same thing, resulting in spaghetti code sprinkled with identical RemoteObject tags.

The Service Locator addresses this purpose; it is a repository of all services that the RIA requires; within it, you can centralize and reuse your service tags.

Furthermore, by using Service Locator, you abstract developers from whether a service is a Java object, a web service, or an HTTP service, and allow them to "treat them all the same." In essence, the Service Locator encapsulates the implementation details of services, leaving application developers simply to request a service from the Service Locator, invoke methods on the service, and handle the results however they might be shipped over the wire.

Cairngorm provides a base-class, ServiceLocator, which is a singleton for service access. In a Cairngorm application, you typically extend this base-class through MXML, with the convention of always having a file called Services.mxml in the business/package of your application.

Let's take a look at Services.mxml for Cairngorm Store:

<cairngorm:ServiceLocator xmlns:mx="http://www.macromedia.com/2003/mxml" xmlns:cairngorm="http://www.iterationtwo.com/cairngorm"> <mx:RemoteObject id="productService" named="productServiceImpl" protocol="http" showBusyCursor="true" result="event.call.resultHandler( event );" fault="event.call.faultHandler( event );"> </mx:RemoteObject>

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 37: Cairngorm

<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 );"

Invoking Services on the Service Locator

Having declared services to reside on the Service Locator, you will obviously want to invoke these services at some point in time.

In Cairngorm Store, let's say you want to fetch all products from the server-side database when the application starts. When the application starts, it executes the GetProductsCommand command; if you recall, you use the creationComplete event on the main Application tag to fire a ShopController.EVENT_GET_PRODUCTS event, which ensures that the application executes the Command class.

Page 38: Cairngorm

In the Service Locator, productService is a Java class that has a method called getProducts(). Calling the getProducts() method returns an array list of ProductVO value objects, which represents all the products that the middleware has pulled out of the underlying database.

In the entry-point for Cairngorm Store, Main.mxml, you instantiate your Service Locator as follows:

<business:Services id="services" />

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{

public function execute( event : Event ):Void { var service = ServiceLocator.getInstance().getService( "productService" ); service.getProducts(); }}

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:   event.call.resultHandler and event.call.faultHandler should be changed to event.token.resultHandler and event.token.faultHandler respectively.

You need to sign in in order to post a comment.

Page 39: Cairngorm

Add Comment

Proxying Services with the Business Delegate

More often than not, a particular service call, such as getProducts(), can be called several times in several different places in the application. Furthermore, the results from these service calls may be used differently, depending upon the context in which the service call was made.

For instance, consider a service call that fetches the day's trading values for a particular stock. One command might render all these data points on a graph, another command might calculate the day's high and low values for the stock, while still another command class might displays the current value of the stock only. In this scenario, you might use three different services calls to service.getStockInformation( "ADBE" ) in these different contexts.

This motivates us to consider how to delegate all the responsibility for business logic that must locate a service and then invoke a method on the service into a separate class. We call that class a Business Delegate.

In Cairngorm Store, there are two Business Delegates, one for each of our services. These kind of design decisions—about how many delegates to have—are entirely up to the individual developer.

Let's look at the implementation of ProductDelegate.as:

class org.nevis.cairngorm.samples.store.business.ProductDelegate{ public function ProductDelegate( responder : Responder ) { this.service = ServiceLocator.getInstance().getService( "productService" ); this.responder = responder; } //---------------------------------------------------------------

public function getProducts() : Void { var call = service.getProducts(); call.resultHandler = Delegate.create( responder, responder.onResult ); call.faultHandler = Delegate.create( responder, responder.onFault ); } //--------------------------------------------------------------- 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 )

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 40: Cairngorm

{ var delegate : ProductDelegate = new ProductDelegate( this ); delegate.getProducts(); } else { // products already loaded, so no need to load them again return; } } //----------------------------------------------------------------------------

public function onResult( event : Object ) : Void { var products : Array = event.result; 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

Page 41: Cairngorm

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.

Where to Go from Here

You have now been exposed to all of the design patterns that contribute and collaborate in the Cairngorm microarchitecture.

Page 42: Cairngorm

You've taken a brief tour of Flex RPC services and uncovered the Service Locator patterns as a useful means of encapsulating all the different services that your rich Internet application might make under a common class in a predictable location.

As legitimate as it may be to invoke these services from the various Command classes in your architecture (you are still free to do so if you so wish) the Cairngorm team has found that there are repeatable benefits to moving all service invocations into a class, one that you delegate all business logic calls to on the server: the Business Delegate.

As well as recognizing the consistency, scalability, modularity, and reuse that the Business Delegate pattern can offer, I discussed strategies for development whereby the Business Delegate becomes a clear contract and "point of integration" between server and client-side development teams.

Finally, I revisited the Command pattern and extended your knowledge of writing commands to include commands that implement the Cairngorm Responder interface. These commands not only use the Business Delegate pattern to invoke services, but they advertise through their Responder interface that they wish to assume the responsibility for handling any results that the Business Delegate receives in proxying service requests on their behalf.

Make sure that you understand the different Flex RPC services available to you and how to write your own Service Locator classes. Make sure you understand how to write your own Business Delegates—by accepting a Responder class in the constructor, liaising with the Service Locator, and adding the necessary incantations to the pending call of each service request to make sure that results are routed back to the Responder.

Finally, I explained the additional methods that you can add to commands that implement the Responder interface to allow commands to take any service call results and update the client-side state, through the Model Locator design pattern, as the final clause in any conversation between the user and the RIA.

In Part 6, the final article in this series, I will summarize and reinforce the Cairngorm architecture. What may have appeared first as complex is actually easy to apply. You'll be able to follow the rapid addition of a completely new feature to the Cairngorm Store, safe in the knowledge that the way you do it adheres to the same best practice that thousands of Flex developers worldwide would also follow when faced with the same code base.

Developing Flex RIAs with Cairngorm Microarchitecture – Part 6: Rapid and Consistent Development with Cairngorm and Flex

Developing Flex RIAs with Cairngorm Microarchitecture – Part 6: Rapid and Consistent Development with Cairngorm and Flex

Steven Webster

Adobe Consultingwww.richinternetapps.com

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 43: Cairngorm

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

Page 44: Cairngorm

 

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 6.

Cairngorm Architecture Review

Let's jump straight in with an end-to-end review of the Cairngorm architecture. The metaphor by which you can refer to the architecture is that Cairngorm simply establishes a rapport between man and machine, listening to what the user wants, ensuring that the user gets what he or she wants, and then communicating that back to the user.

Front Controller Listens for User Gestures

The user is in charge of the conversation. Your rich Internet application awaits some indication from the user as to what to do. These indications—pressing buttons, dragging and dropping icons, double-clicking rows or submitting forms—are all called "user gestures."

Cairngorm translates these user gestures into Cairngorm events. Whenever there is a click, press, drag, drop, or submission of any event that represents a user demand, you use EventBroadcaster to broadcast an event. The event broadcast is the beginning of the Cairngorm conversation.

The Front Controller pattern is the sole listener to Cairngorm events. By centralizing the handling of events, the Front Controller assumes responsibility for meeting the user's demands.

However, the Front Controller doesn't do the work; it's a manager, not a worker. The Front Controller instead maintains a list of "who does what," a list of commands that are best suited to responding to particular events.

Commands Do All the Work

Once the Front Controller recognizes an event for which it has a command on the payroll, as it were, it tells that command to execute. The Front Controller tells each and every command what to do in a consistent manner: the Front Controller calls the execute() method on the command and the command does its specific work.

Delegate Server-Side Business Logic to Business Delegates

Every so often, the command requires the server to perform a duty. In a rich Internet application, some business logic executes on the client side. More often than not, however, you'll want to shift some of the responsibility for business logic to the server.

Commands don't care about how something gets done; they just care that it gets done. Commands prefer to delegate the responsibility for server-side business logic to another class. There is a good

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 45: Cairngorm

chance, after all, that several commands that are trying to achieve distinct tasks of their own will all require the same service for different things. What's more, for all the command knows, the business logic hasn't even been written on the server yet. Maybe the command is simply calling a dummy Business Delegate class, where the server-side infrastructure does not yet exist, but the Business Delegate offers an interface that will exist in the future.

So whenever you have business logic, put it into a Business Delegate class so that your commands can invoke methods on it.

Business Delegate Finds Services with the Service Locator

The Business Delegate provides a seamless interface between commands and any services on the server. These services may be Java services, ColdFusion components, web services, or simple HTTP services. The Business Delegate treats these imposters just the same, invoking them when the command asks and handing results to the command immediately when they become available.

However, the detailed implementation of these services—the names of the Java classes or CFCs, whether they are named or unnamed services, the location of WSDL files or the URLs for HTTP services—are all encapsulated into one single repository of services, called the Service Locator.

The Service Locator is a single directory of all services that your rich Internet application requires. Because only the Business Delegate has responsibility for calling services, only the Business Delegate uses the Service Locator to locate services by name (caring nothing of their implementation) before invoking these services and handing the results to the Command classes.

Remember, any Command classes that assume the reponsibility of responding to service calls and taking the returned data from the Business Delegate must do so by implementing the Responder interface. In this way, the Business Delegate knows that the command will have an onResult() method to handle all results and an onFault() method to handle any errors.

Transfer Data as Value Objects

What happens to the data that is passed between client and server, and held on the client? Cairngorm insists that you treat data with a little respect, encapsulating it in Value Objects that describe what the data is in business terms ("it's an Order") rather than in technical terms ("it's an ArrayList of Objects containing a String and a Number").

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.

Page 46: Cairngorm

Cairngorm Flow of Control

What I have verbosely described above is the flow of control through the various design patterns in a Cairngorm architecture. Each and every feature in an application traces the same flow of execution, with user gestures becoming events, events becoming service calls, and data passing back into the model.

Adding a New Feature to a Cairngorm Application

When you add a new feature to a Cairngorm application, it is as simple as the following process:

1. Register an event and command with the Front Controller using addCommand(). 2. Implement a new command as follows:

a. Implement the execute() method to do the work. 3. Implement the onResult() method to handle any results from the server:

i. Any results are used to update the Model Locator. 4. The Model Locator automatically updates the view. 5. Add any new service calls that the Command requires for the Business Delegate. 6. If these service calls require new services, add them to the Service Locator.

Additionally, you will have to create any Value Objects that are required to pass data between the Command and Business Delegate, between the Business Delegate and the server, or to store data on the Model Locator. Typically, however, your Value Objects will exist early in the development of your application and you will consistently reuse them as you add new features to your application.

Similarly, as your application grows, the server-side services very quickly become locked in place and application development proceeds even more quickly, as follows:

1. Register an event and command with the Front Controller using addCommand(). 2. Implement a new command.

That's really all there is to it.

Debugging a Cairngorm Application

Furthermore, debugging a Cairngorm application follows the same predictable steps. If a desired feature doesn't perform in response to a user gesture, the debug cycle is always the same:

1. Check whether the event is registered with the Front Controller. 2. Check whether the execute() method is called on the command by the Controller. 3. Check that the appropriate delegate methods are called. 4. Check that the onResult() method is called in the command. 5. Check that the model is updated in the Model Locator.

With these five steps, it's painless to isolate a problem in an application and fix it.

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 47: Cairngorm

Cairngorm Store Wish List

One feature often found in e-commerce applications is a "wish list," a temporary list of products that the user might want to purchase in the future without adding them to the shopping cart.

In an agile development team, the requirement or "story" for a Wish List in Cairngorm Store might read as follows:

Cairngorm Store Wish List

In addition to being able to add items from the store into their Shopping Basket, a customers could use a Wish List feature to track items that they wish to purchase in the future.

Using a tab-interface, the customer will be able to choose either a Shopping Basket or a Wish List as the place where they place items. Items placed in the Wish List do not appear in the checkout when the customer proceeds to purchase the items in their shopping basket.

The customer will be able to add items to their Wish List either by dragging a product's image or description from a product list onto the Wish List, by dragging the image from the product details panel onto the Wish List, or by pressing an Add to Wish List button from the product details panel.

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!

Page 48: Cairngorm

Register Event with the Front Controller Class

The very first step is to indicate to the Cairngorm-based application that there is a new user gesture that might occur related to the user, who wants to add products to their wish list.

The Cairngorm Store has it's own Front Controller instance, ShopController.as, which you use to register all the appropriate Cairngorm Store commands.

First, you must add the name of the new event to the ShopController as follows:

public static var EVENT_ADD_PRODUCT_TO_WISH_LIST = "addProductToWishList";

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.

Creating the AddProductToWishListCommand Command Class

Let's just look at the code for the new command class, as it is so simple and self-explanatory:

import org.nevis.cairngorm.commands.Command;import org.nevis.cairngorm.control.Event;import org.nevis.cairngorm.samples.store.model.ModelLocator;import org.nevis.cairngorm.samples.store.vo.ProductVO; 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.

Page 49: Cairngorm

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.

Store the WishList Object in ModelLocator

Cairngorm makes Flex development embarassingly simple from this point forward. Since you already have a ShoppingCart class in Cairngorm Store that maintains a list of products and quanties of those products, there is absolutely no reason you cannot simply reuse this class to implement the WishList object. A WishList object is after all, simply a Shopping Cart that you don't necessarily want to take to the checkout on this particular visit.

So here's the code you add to ModelLocator.as:

Quite simply, you create a new instance of the ShoppingCart class and call it wishList instance. You create the new instance in ModelLocator's constructor, as follows:

ModelLocator.wishList = new ShoppingCart();

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?

Implement the View

So we have a Command class created, it's registered with the Front Controller class, it updates the Model Locator class with products added to the wishlist, and you've reused a business object (ShoppingCart) to implement our the wish list.

All that remains is creating the user interface for the wish list to ensure that it's dynamically bound to ModelLocator for the presentation of dynamic data, and to ensure that any user gestures that occur on the interface result in our ShopController.EVENT_ADD_PRODUCT_TO_WISH_LIST being broadcast.

Adding the Wish List to the View

I'm going to replace the area of screen that only shows the ShoppingCart MXML component with a Tab Navigator that contains two instances of the ShoppingCart MXML component. The first of these instances is our original shopping cart, while the second will be the wish list.

You'll alter the ProductDetails component to capture when the user has gestured to add a product to the wish list, as well as adding a product to the shopping cart. This will require that the ProductDetails MXML component now have an addProductToWishList event to which you can hook code, as shown in the highlighted code below.

This simply requires us altering SideArea.mxml as follows:

<details:ProductDetails

Page 50: Cairngorm

id="productDetailsComp" width="100%" height="325" currencyFormatter="{ ModelLocator.currencyFormatter }" selectedItem="{ ModelLocator.selectedItem }" addProduct="addProductToShoppingCart( event )" addProductToWishList="addProductToWishList( event )" /> <mx:TabNavigator width="100%" height="100%">

<!-- the Shopping Cart --> <cart:ShoppingCart id="shoppingCartComp" label="Shopping Cart" width="100%" height="100%" shoppingCart="{ ModelLocator.shoppingCart }" selectedItem="{ ModelLocator.selectedItem }" select="ModelLocator.selectedItem = event.target.selectedItem" currencyFormatter="{ ModelLocator.currencyFormatter }" addProduct="addProductToShoppingCart( event );" deleteProduct="deleteProductFromShoppingCart( event );" gotoCheckout="ModelLocator.workflowState = ModelLocator.VIEWING_CHECKOUT;" />

<!-- the Wish List --> <cart:ShoppingCart id="wishListCartComp" label="Wish List" width="100%" height="100%" shoppingCart="{ ModelLocator.wishList }" selectedItem="{ ModelLocator.selectedItem }" select="ModelLocator.selectedItem = event.target.selectedItem" currencyFormatter="{ ModelLocator.currencyFormatter }" addProduct="addProductToWishList( event );" deleteProduct="deleteProductFromShoppingCart( event );" gotoCheckout="ModelLocator.workflowState = ModelLocator.VIEWING_CHECKOUT;" />

</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 );}

Page 51: Cairngorm

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.

, 01/03/-1,
HTML: </NOSCRIPT>
, 01/03/-1,
HTML: <NOSCRIPT>
Page 52: Cairngorm

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.

Where to Go from Here

Cairngorm continues to evolve with the Flex platform.

We will be releasing Cairngorm 2.0 to accompany the release of Flex 2.0. In the meantime, there is a beta release of Cairngorm 2.0 that is tracking the beta cycle of Flex 2.0, that you can get at www.richinternetapps.com/archives/000143.html.

Adobe Consulting is committed to continuing to invest in the support of the Cairngorm platform, blending real-world field experience with the Flex product engineers and community members who are actively engaged in the Cairngorm committee.

Page 53: Cairngorm

The active members of the Cairngorm committee are all regular contributers to the Flexcoders mailing list, where you can ask any of your Flex and Cairngorm-related questions.

For those interested in how Cairngorm has been implemented, see the book Developing Rich Clients with Macromedia Flex by Steven Webster and Alistair McLeod, which covers many of the concepts behind Cairngorm in significant detail.