Remote Synthesis
Search my blog:
Viewing By Entry / Main
Oct 07, 2008

Cairngorm Crystallized

This is a reprint with permission of an article that was originally published in the Fusion Authority Quarterly Update Volume 2 Issue 3 in October 2007 (click here to subscribe). If you would like to download the source you can find it here. I have another Cairngorm sample appliction which you can get via Subversion at http://code.google.com/p/remotesynthesis/ where you will also find the same example built with the Mate Framework. If you are interested in the Mate Framework, you can find my article on that topic in Flex Authority Volume 1 Issue 1, which you can subscribe to here.

Examining the Cairngorm Microarchitecture from a Workflow Perspective

by Brian Rinaldi and Michael Corbridge

What is Cairngorm?
Cairngorm is a framework for rich Internet application (RIA) development. More specifically, Cairngorm is a package of Actionscript 3.0 classes that help implement a number of proven design patterns to create a generic starting point for your Flex application architecture. By adapting a number of proven J2EE solutions for the RIA world, Cairngorm helps you deal with the complexity of managing state and encapsulating your business logic into reusable components while taking into account the asynchronous nature of service requests within a Flex application.

By providing this consistent architecture and proven design patterns, using Cairngorm can offer a number of benefits to your Flex application, including:

  1. A centralized model (i.e. the Model Locator), which helps to decouple code and improve reusability
  2. A centralized event/response system, which helps to do this as well
  3. The separation of concerns along the lines of the Model-View-Controller pattern, which helps to isolate business logic and simplify maintenance
  4. A straightforward and documented structure for application building within a team environment

By focusing on the Cairngorm workflow, this article is intended to illustrate the "how" of Cairngorm as opposed to focusing heavily on the "why." Building an application with Cairngorm tends to follow a repetitive sequence of steps that, once they are clear, make Flex development a fairly simple and organized process. The intent of this article is to walk you through these steps.

Sidebar: Who Created Cairngorm?

Cairngorm was created by Steven Webster and Alistair McLeod when both were principals of iteration::two, a company that is now part of Adobe Consulting. The ideas for the Cairngorm framework first appeared in their book Reality J2EE - Architecting for Flash MX, released by Macromedia Press in 2002.  In their book, Developing Rich Clients with Macromedia Flex, a more mature version of Cairngorm was described to specifically address the challenges of developing enterprise-level Flex applications. Cairngorm evolved as a means of organizing the callbacks associated with the asynchronous nature of remote method invocation (RMI), and making the service calls responsible for RMI centrally available within the application.

Setting Up Cairngorm

You can find Cairngorm at Adobe labs (http://labs.adobe.com/wiki/index.php/Cairngorm). Scroll down to the Downloads section and pull the zip for Cairngorm 2.2, which is the most recent version as of this writing.

Place the Cairngorm.swc file found in the bin folder of the downloaded cairngorm2_2.zip anywhere on your computer. I chose to place mine in my inetpub directory.  An ActionScript .swc file is analogous to a Java .jar file; by deploying the .swc file, the developers are stating that these are core classes that cannot be modified. Once you have created your Flex project in Flex Builder, right click on the project, select "Properties > Flex Build Path" and choose the "Library Path" tab. Next click on the "Add SWC" button, locate your Cairngorm.swc and click ok.

Basic Cairngorm Terms and Concepts
The interactions within a Flex application based on Cairngorm can be broken down into four fundamental classes:

1. Control (FrontController): This class maintains a registry of all possible service calls within the application. Any request for data or operations within the application must first go through the FrontController, which detects an event and determines which Command class to run. If you are familiar with ColdFusion frameworks such as Mach ii, Model-Glue or Fusebox, the concept of the front controller remains the same - it basically determines where to route the request.

2. Command: The Command class is called by the FrontController to execute the service call via a third class called the Delegate, returning the data to a result() function, if successful, or a fault() function, in the event of an error. 

3. Delegate: The Delegate class encapsulates access to a business service. It is an ActionScript implementation of a common J2EE pattern where the details of the business service, such as lookup and access to the ColdFusion middle tier, are contained. By abstracting these service calls in a Delegate, it is simple to point the application at an alternate source, such as a Java service, if necessary.

4. Model: The state of the application and its variables is maintained by this singleton class, usually given the filename 'ModelLocator'.  Using the 'getInstanceof' method, the model can be imported into any class, or mxml template, within the application.  ModelLocator uses Flex's data binding so that any component in the application that is bound to the model is immediately updated when a variable within the model is updated by an event result in the command class.

The Task At Hand
Our sample application is designed to pull a list of ColdFusion's built-in functions and then, once one is selected, return the LiveDocs documentation for that item, much like cfQuickDocs.com does. It has two beans or value objects: [Footnote 1: A value object is the Flex equivalent of a bean.] a "cffunction" object that represents a ColdFusion built-in function and a LiveDoc object that represents a content page from the LiveDocs site. A service component handles all of our business logic and a remoting facade component communicates with Flex via ColdFusion's built-in Flash Remoting.

Since this article is focused on Cairngorm and its workflow, we won't go into detail about the ColdFusion code. You can download all of the code for this application from http://www.fusionauthority.com/quarterly/volume2issue2. However, there are a few things to remember when building the ColdFusion back-end to your Flex/Cairngorm application:

Creating Components (CFCs) for Use with Flex and Cairngorm
The first thing you need to understand when working with Cairngorm is that, while you could technically use it without proper object-oriented service and model layers, it isn't recommended.  (For an understanding of OO concepts and the creation of separate service and model layers in your application, see Fusion Authority Quarterly Update Volume 1 Issue 2, particularly Chris Scott's article on ColdSpring.) Flex and Cairngorm expect you to deal in objects or arrays of objects, and in our experience you will find things work much more smoothly this way. For example, when you return an array of component instances, Flex will automatically translate it into an array of value objects, which can easily be used to populate a Flex array collection.

Organization
This is more a recommendation than a requirement. However, you will want to keep a nice, clean separation of your ColdFusion code from your Flex code. Cairngorm tends to generate a lot of components and classes within your application, and a disorganized components directory that is a mix of ColdFusion and Flex code could become unwieldy. Typically, we recommend putting all your Flex and ColdFusion components within a /com directory under the root, with all ColdFusion components under the /com/cf subdirectory. In a larger application, the /cf directory should be further subdivided based upon the object model of the application.

Instantiation
In order to get the data type translation between ColdFusion and Flex functioning, be sure to always use the full dot notation path to a component when instantiating it. If you are like us, you may prefer to use relative dot notation paths whenever possible to make your code a little more portable. However, this will not work if your component is intended to be automatically converted to a value object on the Flex side. So, following the organization discussed above, you could technically instantiate a docsPage object within the docsService component by doing createObject("component","docsPage"), since both the service and the bean CFCs lie in the root of /com/cf/ directory. Nonetheless, you must use createObject("component","com.cf.docsPage") for this component to be automatically translated into a DocsPage value object in Flex. We will look at how you create this value object later.

MetaData
The Flex/ColdFusion adapter will automatically translate property values as well as types when your component instance is translated into a value object. However, it requires that you include the cfproperty metadata tags in your component. For example, our ColdFusion function object (/com/cf/cffunction.cfc) has two properties:

 Listing 1: Properties of cffunction.cfc

<cfproperty name="functionName" type="string" default="">
<cfproperty name="docsURL" type="string" default="">

If you remember these important items, the ColdFusion/Flex data mapping should work perfectly every time.

Building Our Sample Application

As discussed briefly above, the goal of our application is very simple. First, we will populate a drop-down combobox with a list of ColdFusion's built-in functions and, when one is selected, we will fill a text area with the content of that function's documentation from the ColdFusion LiveDocs site.

Figure 1: A View of Our Finished Sample Application

In this section, we will focus on the workflow of getting that done in Cairngorm. While each person might individualize this process, what follows is a typical sequence, though as you become more comfortable with the process you can adjust it to your needs. As we walk you through these steps, you can refer to Figure 3 at the end of this article, so that you know exactly what we're doing and where it fits into the Cairngorm microarchitecture.

Step 1 - Function
Cairngorm has a built-in event dispatcher, CairngormEventDispatcher, that allows you to centralize and reuse code throughout your application rather than repeat business logic within each function call. For example, on initialization, our application requires a list of the available ColdFusion functions. Thus, in our mx:Application tag, we have included creationComplete="initApp()", which means that the function initApp() will be called when the application has finished loading. Our initApp() function creates a variable called cffunctionEvent which is of type GetCfFunctionsEvent.

This event takes no parameters, so we simply pass it off, as is, to our event dispatcher:

Listing 2: the event dispatcher

CairngormEventDispatcher.getInstance().dispatchEvent(cffunctionEvent)

Effectively what this line of code is doing is announcing an event to whatever part of the application may be listening for it. However, right now nothing is listening for it; in fact, the event we announced (cffunctionEvent) doesn't even exist yet, so let's create one.

Step 2 - Event
This event object uses a standard "template" of sorts for Cairngorm events and, in this case, has no properties, since our ColdFusion component method to get the list of available ColdFusion functions takes no arguments.

Listing 3: com.control

package com.control
{
    import com.adobe.cairngorm.control.CairngormEvent;
   
    public class GetCfFunctionsEvent extends CairngormEvent
    {
        public function GetCfFunctionsEvent()
        {
            super(DocsControl.EVENT_GET_CFFUNCTIONS);
        }
    }   
}

The event we are dispatching extends CairngormEvent, which in turn extends the Flash 'Event' class.  The Event constructor takes as an argument a string that describes the event to be dispatched.  This string is stored in the control class 'DocsControl' as the string: DocsControl.EVENT_GET_CFFUNCTIONS.

Step 3 - Controller
EVENT_GET_CFFUNCTIONS is simply a static variable within the Controller which further binds our event to its appropriate "handler" or command. First, we must create this variable by placing the following line before our constructor method:

Listing 4: EVENT_GET_CFFUNCCTIONS

public static const EVENT_GET_CFFUNCTIONS : String = "EVENT_GET_CFFUNCTIONS";

Next we "link" that to the appropriate command within the constructor with:

addCommand(DocsControl.EVENT_GET_CFFUNCTIONS, GetCfFunctionsCommand);

The 'addCommand' method serves two purposes. First, it ensures that the command is unique. Remember that the Cairngorm framework is designed for an enterprise development environment where a number of developers are working on the same code base. If a developer attempts to re-register the event, addCommand will throw an error indicating that the command is already registered. If it passes this test, the Flash 'addEventListener' method registers this event through the CairngormEventDispatcher singleton. Thus the cffunctionEvent you announced earlier is now bound to the appropriate command class.

Step 4 - Command
The Command class is where the guts of our application will live, and it will always have three functions: execute; result; and fault. The execute function will handle any business logic or other general application logic that needs to happen prior to our remote method call (i.e. the ColdFusion component method call). The result function will do the same with the data returned by the remote method call. The result will also usually "attach" this data to the ModelLocator which, as discussed above, allows different parts of your application to bind to data without necessarily knowing where it comes from.

Finally, the fault method is simply our error handler should anything go wrong with the remote method call.

Listing 5: The execute function 

public function execute( cgEvent:CairngormEvent ) : void {
    var delegate : DocsServicesDelegate = new DocsServicesDelegate( this ); 
    delegate.getCfFunctions();
}
       

public function result( rpcEvent : Object ) : void {
       model.cffunctions.source = rpcEvent.result as Array;
}

public function fault( rpcEvent : Object ) : void {
    mx.controls.Alert.show("Fault occured in GetCfFunctionsCommand.");
    mx.controls.Alert.show(rpcEvent.fault.faultCode);
    mx.controls.Alert.show(rpcEvent.fault.faultString);
}

The code above brings together a number of the concepts we have discussed. First, the execute method calls the appropriate method call within the delegate class, whose job it is to abstract the actual remote service call. Next, the result method binds the data returned by the remote call to the model, whereby all the relevant data bindings are automatically refreshed. Finally, the fault method simply states where the error occurred and outputs the details of that error in alert boxes.

Step 6 - Delegate

The Delegate's job is to store remote services and make the appropriate asynchronous calls. For instance, our DocsServicesDelegate (see Listing 5 line 2) holds the docsServices RemoteObject which, as we will discuss next, contains the remote call details for our docsRemote ColdFusion component methods. The line in the constructor that defines this relationship is:

this.service = ServiceLocator.getInstance().getRemoteObject( 'docsServices' );

Our delegate's methods match the remote method calls and therefore we have the getCfFunctions method, which makes the asynchronous call to getCfFunctions within our ColdFusion component. Remember, getCfFunctions doesn't take any arguments; later we will look at an example where we need to pass data to ColdFusion.

Listing 6: getCfFunctions

public function getCfFunctions() : void {
    // call the service
    var token:AsyncToken = service.getCfFunctions.send();
    // notify this command when the service call completes
    token.addResponder( command );
}

It is important to point out here that the Delegate within our sample application deviates slightly from the standard practice, as it wraps up multiple method calls. This is a little bit of economizing and improvising on our part and suits a small application. However, when building a large application you may want to consider breaking your Delegates apart so that each one represents a single method call.

Step 7 - Service

Our service (Services.mxml) contains the necessary remote object calls to connect Flex and ColdFusion. In this case, we will have one RemoteObject with two methods:

Listing 7: RemoteObject

<mx:RemoteObject
    id="docsServices"
    destination="ColdFusion"
    source="cairngormcf.com.cf.docsRemote">
    <mx:method name="getCfFunctions" />
    <mx:method name="getFunctionDoc" />
</mx:RemoteObject>

Step 8 - Value Object
If you have taken a look at the ColdFusion code, you will notice that the getCfFunctions() method returns an array of cffunction.cfc instances.

<cffunction name="getCfFunctions" access="public" output="false" returntype="array">
    <cfset var i = 0 />
    <cfset var functionList = getFunctionList() />
    <cfset var cfFunctionListArray = arrayNew(1) />
    <cfset var functionName = "" />
    <cfset var thisURL = "" />
    <cfset var sortedFunctions = listToArray(structKeyList(functionList)) />
    <cfset arraySort(sortedFunctions,"text")>

    <cfloop from="1" to="#arrayLen(sortedFunctions)#" index="i">
        <!--- get the docs url --->
        <cfset thisURL = getFunctionURL(sortedFunctions[i]) />
        <cfif len(thisURL)>
            <!--- must use fully qualified dot notation path --->
            <cfset arrayAppend(cfFunctionListArray,createObject("component","cairngormcf.com.cf.cffunction").init(sortedFunctions[i],thisURL)) />
        </cfif>
    </cfloop>
    <cfreturn cfFunctionListArray />     
</cffunction>

The cffunction.cfc is a simple bean - though it does not have to be. It does, however, contain the cfproperty tags representing its object properties. If you have the ColdFusion extensions for Flex Builder installed, the next step is quite easy. Right-click on the cffunction.cfc and go to "ColdFusion Wizards > Create AS class (based on CFC)". This wizard will create a Flex value object based upon the ColdFusion component metadata. Simply supply the location (/com/vo/), the class name (cffunctionVO.as) and the full dot notation path to the ColdFusion component it represents (cairngormcf.com.cf.cffunction). Now whenever an instance of cffunction.cfc is passed to Flex, it will be automatically converted to an instance of cffunctionVO.as within Flex.

Rinse and Repeat
At first glance, this may seem like quite a lot of code to manage, but once you get into the workflow of Cairngorm you start to realize that 1) all these pieces are necessary; and 2) this isn't nearly as difficult or time consuming as it seems. We've now added the functionality that fills our drop down ComboBox when the application first loads. As a review, let's quickly cover how we would add the functionality whereby when you choose a ColdFusion function name from the combobox, it grabs the appropriate documentation from the LiveDocs.

Our FunctionSelectPanel view component contains the ComboBox display code. We first add a change event to the ComboBox that calls getDocs() when an item is selected.

Since the ComboBox was populated with an ArrayCollection of cffunctionVO value object instances, the selectedItem will be an instance of cffunctionVO. Just as cffunction.cfc was automatically converted to cffunctionVO.as, the reverse is also true and our cffunctionVO instance will become a cffunction.cfc instance when passed to ColdFusion.

Therefore, our getFunctionDoc method in our docsRemote component takes an instance of cffunction.cfc and our GetFunctionDocEvent will take an instance of cffunctionVO as follows:

Listing 8:

private function getDocs() :void {
    var cffunction : cffunctionVO = functionSelect.selectedItem as cffunctionVO;
    var event : GetFunctionDocEvent = new GetFunctionDocEvent(cffunction);
    CairngormEventDispatcher.getInstance().dispatchEvent( event );
}

Since the GetFunctionDocEvent takes an argument, the event template needs to account for this argument as a property of the event object, as shown below in Listing 9 (line 10). However the controller code still follows the same pattern discussed above:

Listing 9: com.control

package com.control
{
    import com.adobe.cairngorm.control.CairngormEvent;
    import com.vo.cffunctionVO;
   
    public class GetFunctionDocEvent extends CairngormEvent
    {
        public var cffunction : cffunctionVO;
       
        public function GetFunctionDocEvent(cffunction:cffunctionVO)
        {
            super(DocsControl.EVENT_GET_FUNCTIONDOC);
            this.cffunction = cffunction;
        }
    }   
}

The execute method of our command also accounts for this argument by creating an instance of our event and passing the value on to the Delegate:

Listing 10: execute

public function execute( cgEvent:CairngormEvent ) : void {
    var delegate : DocsServicesDelegate = new DocsServicesDelegate( this );
    var getFunctionDocEvent : GetFunctionDocEvent = GetFunctionDocEvent( cgEvent ); 
    delegate.getFunctionDoc(getFunctionDocEvent.cffunction);
}

The matching Delegate method simply takes the data and passes it along to the remote object call:

Listing 11:

public function getFunctionDoc(cffunction:cffunctionVO) : void {
    var token:AsyncToken = service.getFunctionDoc.send(cffunction);
    token.addResponder( command );
}

Since getFunctionDoc returns an instance of the docsPage.cfc, we also need to create the docsPageVO.as, using the ColdFusion wizard in the same manner outlined above.

Conclusion
I hope that the above has illustrated how Cairngorm can help establish an organized workflow with Flex to send, receive and respond to data with ColdFusion. While it can seem overwhelming at first, once you have established the workflow I believe you will find developing with Cairngorm to be very straightforward. In addition, once you have developed your first application I think the benefits discussed at the beginning of this article will become very clear.

If you would like to learn more about Cairngorm, I recommend the following resources:

Comments
Andrew
I've taken to making event names a part of my event class. I'm curious what the argument is for putting a hard-coded event-name string in the controller?
ie
<quote>
   public class SubmitWithCommentsEvent extends CairngormEvent
   {
public static var SUBMITWITHCOMMENTSEVENT:String = "_SUBMIT_WITH_COMMENTS_EVENT";
...
</quote>

In your listing 9, the event class becomes bound to your controller, making it trickier for code-reuse of your event class. To better encapsulate the event, and how an event defines itself, I define the event name in the event class. I'd be happy to learn why one might be better than the other.


Brian Rinaldi
You are absolutely right. This article was originally written quite some time ago (its a reprint) and it used the style from the original Cairngorm tutorials. However, the method you are recommending is exactly what I do nowadays and makes much more sense.


Write your comment



(it will not be displayed)