Cairngorm

The architectural framework Cairngorm was created by Alistair McLeod and Steven Webster while they were working at the company iteration::two (they are presently employed by Adobe Consulting). Cairngorm implements several design patterns such as MVC, Command, and Delegate. It was open sourced in the summer of 2008.

Cairngorm was designed to ensure that UI components do not need to know where data is located. The business layer retrieves data from the servers and stores it in the memory objects that represent the data model, which use binding to notify the UI components about data arrival or changes. On the same note, changes in the UI are propagated to the server side through this business layer.

The Cairngorm framework promotes the use of the MVC design pattern in the client portion of your RIA. It offers a number of classes implementing Model, View, and Controller tiers, and interaction between them.

The Model tier is represented by the class ModelLocator, which stores the application-specific data (these are often collections of value objects, a.k.a. data transfer objects). ModelLocator’s data is bound to the View controls.

The View portion contains visual components required by your application, value objects, and Cairngorm-specific event classes used for communication with the Model and Controller tiers.

The Controller tier is responsible for invoking appropriate code containing the business logic of your application, which is implemented by using global FrontController and ServiceLocator classes as well as additional Command and Delegate classes.

The Cairngorm framework’s documentation and sample applications are located at http://www.cairngormdocs.org.

Note

As this chapter was being written, Adobe decided to rebrand Cairngorm; instead of a mere framework, Adobe is promoting it as a set of tools and methodologies containing various frameworks, including what has been earlier known as the “Cairngorm framework.” You can read about this Cairngorm 3 initiative at http://opensource.adobe.com/wiki/display/cairngorm/Cairngorm+3. In this chapter, we refer to Cairngorm 2, which is an MVC Flex framework and nothing else.

Café Townsend with Cairngorm

The “pure Flex” code shown in Example 1-1 includes representatives of each MVC tier. The code knows that the data will be loaded into an ArrayCollection (the Model) by the HTTP service pointing at the Employees.xml file by calling a send() method on the creationComplete event (the Controller) of the application. The List component (the View) knows about its model and is bound to it directly via its dataProvider property.

The data flow between Cairngorm components while displaying a list of Café employees is depicted in Figure 1-4.

Cairngorm employee list data flow

Figure 1-4. Cairngorm employee list data flow

The Cairngorm version of this application has the following six major participants:

Services

The UI portion does not know about implementation of services and can’t call them directly, so you must move the HTTPService object into a special file called Services.mxml.

FrontController

The View and the service layer can’t send events to each other directly, but rather have to be registered with a singleton FrontController that maps all application events to appropriate actions (commands).

Command

When a View component fires an event, FrontController finds the Command class that was registered with this event and calls its method execute().

Delegate

The method execute() of the Command class creates an instance of the Delegate class that knows which service to call (HTTPService, RemoteObject, WebService) and returns the result or fault to the Command class.

ModelLocator

The Command class updates the data in the model (typically, a collection of value objects) defined in the global ModelLocator.

View

Because each model located inside the ModelLocator is bound to a UI control, its content gets updated automatically.

Use the source code of the Café Townsend Multi-View Contact Management application that was converted to Cairngorm 2 by Darren Houle and is available under the Creative Commons license. You can download the source code of this application at http://cairngormdocs.org/blog/?p=19.

Figure 1-5 is a screenshot of the Café Townsend Flash Builder project. Please note that the code for the six participants mentioned earlier is organized in separate packages (folders). The business folder is for delegates and service components. The command folder is for Command classes; control is for events and FrontController; the ModelLocator is located in the model folder; and the view folder has visual components as shown in Figures 1-1 through 1-3. The value objects of the application have been placed in the folder called vo. Regardless of what framework you are going to use, separating various application components in project subfolders helps make the project more organized.

Café Townsend Cairngorm project structure

Figure 1-5. Café Townsend Cairngorm project structure

To make Cairngorm classes available to your application, just download Cairngorm’s compiled version (binary) and add cairngorm.swc to the Library path of your Flex project (use the Flex Build Path menu under your project’s properties).

Let’s get familiar with the Cairngorm workflow by tracing the data and events starting from the main application object of Café Townsend, shown in Example 1-2. Please note the use of four global objects: AppModelLocator, Services, AppController, and CairngormEventDispatcher.

Example 1-2. The application file of Café Townsend

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!--
  Cafe Townsend MVC Tutorial © 2006 Adobe
  Converted to Cairngorm 2 by Darren Houle
   lokka_@hotmail.com   http://www.digimmersion.com
  This is released under a Creative Commons license.
  http://creativecommons.org/licenses/by/2.5/
-->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:business="com.adobe.cafetownsend.business.*"
   xmlns:control="com.adobe.cafetownsend.control.*"
  xmlns:view="com.adobe.cafetownsend.view.*" backgroundColor="#000000"
  creationComplete="loadEmployees();" layout="vertical
   viewSourceURL="srcview/index.html">

   <mx:Script>
      <![CDATA[
      import com.adobe.cairngorm.control.CairngormEventDispatcher;
      import com.adobe.cafetownsend.control.LoadEmployeesEvent;
      import com.adobe.cafetownsend.model.AppModelLocator;

      [Bindable]
      private var model: AppModelLocator =
                  AppModelLocator.getInstance();

      private function loadEmployees() : void {
       var cgEvent : LoadEmployeesEvent = new LoadEmployeesEvent();
        CairngormEventDispatcher.getInstance().dispatchEvent(cgEvent);
      }
      ]]>
   </mx:Script>

   <business:Services id="services"/>

   <control:AppController id="appController"/>

   <mx:Style source="assets/main.css"/>
   <mx:Image source="assets/header.jpg" width="700"/>
   <mx:HBox backgroundColor="#ffffff" paddingBottom="10" paddingLeft="10"
               paddingRight="10" paddingTop="10" width="700">
   <mx:VBox paddingRight="10" verticalScrollPolicy="off" width="100%">
    <mx:ViewStack paddingBottom="10" paddingTop="10" resizeToContent="true"
                selectedIndex="{model.viewing}" width="100%">
      <view:EmployeeLogin/>
      <view:EmployeeList/>
      <view:EmployeeDetail/>
    </mx:ViewStack>
   </mx:VBox>
  </mx:HBox>
</mx:Application>

In the example code, CairngormEventDispatcher dispatches the cgEvent:

CairngormEventDispatcher.getInstance().dispatchEvent(cgEvent);

Cairngorm’s front controller (AppController) creates an instance of a command class that was registered to process this event (see Example 1-4 later).

To eliminate the need to import CairngormEventDispatcher in every view, starting from Cairngorm 2.2 you can call the dispatch() method on the event itself, which uses CairngormEventDispatcher internally, that is:

cgEvent.dispatch();

The three views of the Café Townsend application object are implemented as components located in the ViewStack container.

On the application startup, the code dispatches LoadEmployeesEvent and, as if by magic, the EmployeeList gets populated from Employees.xml. How did it happen? LoadEmployeesEvent is a subclass of CairngormEvent (Example 1-3).

Example 1-3. The class LoadEmployeesEvent

package com.adobe.cafetownsend.control {

   import com.adobe.cairngorm.control.CairngormEvent;
   import com.adobe.cafetownsend.control.AppController;

   public class LoadEmployeesEvent extends CairngormEvent {

      public function LoadEmployeesEvent() {
         super( AppController.LOAD_EMPLOYEES_EVENT );
         }
      }
}

This class creates an event with an ID AppController.LOAD_EMPLOYEES_EVENT, which among other events has been registered and mapped to the command LoadEmployeesCommand in the global AppController implementation shown in Example 1-4.

Example 1-4. The AppController implementation

package com.adobe.cafetownsend.control {

   import com.adobe.cairngorm.control.FrontController;
   import com.adobe.cafetownsend.command.*;

   public class AppController extends FrontController {

      public static const LOAD_EMPLOYEES_EVENT : String =
         "LOAD_EMPLOYEES_EVENT";
      public static const LOGIN_EMPLOYEE_EVENT : String =
         "LOGIN_EMPLOYEE_EVENT";
      public static const ADD_NEW_EMPLOYEE_EVENT : String =
        "ADD_NEW_EMPLOYEE_EVENT";
      public static const UPDATE_EMPLOYEE_EVENT : String =
        "UPDATE_EMPLOYEE_EVENT";
      public static const LOGOUT_EVENT : String =
         "LOGOUT_EVENT";
      public static const CANCEL_EMPLOYEE_EDITS_EVENT : String =
        "CANCEL_EMPLOYEE_EDITS_EVENT";
      public static const DELETE_EMPLOYEE_EVENT : String =
         "DELETE_EMPLOYEE_EVENT";
      public static const SAVE_EMPLOYEE_EDITS_EVENT : String =
        "SAVE_EMPLOYEE_EDITS_EVENT";

 public function AppController() {
  addCommand( AppController.LOAD_EMPLOYEES_EVENT, LoadEmployeesCommand );
  addCommand( AppController.LOGIN_EMPLOYEE_EVENT, LoginEmployeeCommand );
  addCommand( AppController.ADD_NEW_EMPLOYEE_EVENT, AddNewEmployeeCommand );
  addCommand( AppController.UPDATE_EMPLOYEE_EVENT, UpdateEmployeeCommand );
  addCommand( AppController.LOGOUT_EVENT, LogoutCommand );
  addCommand( AppController.CANCEL_EMPLOYEE_EDITS_EVENT,
                        CancelEmployeeEditsCommand );
  addCommand( AppController.DELETE_EMPLOYEE_EVENT, DeleteEmployeeCommand );
  addCommand( AppController.SAVE_EMPLOYEE_EDITS_EVENT,
                       SaveEmployeeEditsCommand );
 }
 }
}

The next point of interest is the class LoadEmployeesCommand. This command class implements the Command implementation (Example 1-5), which forces you to implement the method execute(), which can invoke the right delegate class that has the knowledge of “who to talk to” when a specific command has been received. The method execute() must have an argument—the instance of the CairngormEvent object that may or may not encapsulate some application data (for example, some value object that is not used in our scenario).

It also implements the interface IResponder, which requires you to add the result() and fault() methods. By using these callbacks the delegate will return to the command class the result (or error information) of the execution of the command in question.

Example 1-5. The Command implementation

package com.adobe.cafetownsend.command {

   import mx.rpc.IResponder;
   import com.adobe.cairngorm.commands.Command;
   import com.adobe.cairngorm.control.CairngormEvent;
   import com.adobe.cafetownsend.business.LoadEmployeesDelegate;
   import com.adobe.cafetownsend.model.AppModelLocator;

 public class LoadEmployeesCommand implements Command, IResponder {

   private var model : AppModelLocator = AppModelLocator.getInstance();

   public function execute( cgEvent:CairngormEvent ) : void {

   // create a worker who will go get some data
   // pass it a reference to this command so the delegate
   // knows where to return the data
    var delegate : LoadEmployeesDelegate = new LoadEmployeesDelegate(this);

   // make the delegate do some work
   delegate.loadEmployeesService();
   }

   // this is called when the delegate receives a result from the service
   public function result( rpcEvent : Object ) : void {
   // populate the employee list in the model locator with
   // the results from the service call
    model.employeeListDP = rpcEvent.result.employees.employee;
   }

   // this is called when the delegate receives a fault from the service
   public function fault( rpcEvent : Object ) : void {
   // store an error message in the model locator
   // labels, alerts, etc. can bind to this to notify the user of errors
    model.errorStatus = "Fault occured in LoadEmployeesCommand.";
   }
 }
}

Because this version of the Café Townsend application uses the HTTPService request for retrieval, Flex automatically converts Employees.xml into ArrayCollection and does not use the value object Employee.as. This leads to the need for additional coding to convert the data to appropriate types. For example, employee startDate will be stored as a string and will require code to convert it to Date if any date manipulations will be needed.

If you’ll be using Cairngorm in your projects, consider simplifying the application design by eliminating the delegate classes. Just move the business logic from the delegate right into the execute() method of the command class itself.

Create a common ancestor to all your commands and define the fault method there to avoid repeating the same code in each command class.

To load the employees, the Command class creates an instance of the proper delegate passing the reference to itself (this is how the delegate knows where to return the data) and calls the method loadEmployeesService():

var delegate : LoadEmployeesDelegate = new LoadEmployeesDelegate(this);
delegate.loadEmployeesService();

Have you noticed that the Command class has also reached for the AppModelLocator to be able to update the model?

private var model : AppModelLocator = AppModelLocator.getInstance();
...
model.employeeListDP = rpcEvent.result.employees.employee;
...
 model.errorStatus = "Fault occured in LoadEmployeesCommand.";

Now, let’s take a peek into the Delegate class from Example 1-6. It gets a hold of the global ServiceLocator class, the only player who knows about who’s hiding behind the mysterious name loadEmployeesService. The method loadEmployeesService() sends the request to the execution and assigns the responder (the instance of LoadEmployeesCommand), engaging the AsyncToken design pattern described in Chapter 2.

Example 1-6. The Delegate implementation

package com.adobe.cafetownsend.business {

   import mx.rpc.AsyncToken;
   import mx.rpc.IResponder;
   import com.adobe.cairngorm.business.ServiceLocator;

 public class LoadEmployeesDelegate {

       private var command : IResponder;
   private var service : Object;

   public function LoadEmployeesDelegate( command : IResponder ) {
    //constructor will store a reference to the service we're going to call
    this.service = ServiceLocator.getInstance().getHTTPService(
                          'loadEmployeesService' );
    // and store a reference to the command that created this delegate
    this.command = command;
   }

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

As mentioned previously, each Cairngorm application has a central registry that knows about each and every service that may be used by the application (Example 1-7).

Example 1-7. The Services implementation

<?xml version="1.0" encoding="utf-8"?>
<cairngorm:ServiceLocator
   xmlns:mx="http://www.adobe.com/2006/mxml"
   xmlns:cairngorm="com.adobe.cairngorm.business.*">

   <mx:HTTPService id="loadEmployeesService" url="assets/Employees.xml" />

</cairngorm:ServiceLocator>

In our case it’s just one HTTPService, but in a real-world scenario, the Services.mxml file may list dozens of services. As every service must have a unique ID (in our case, it’s loadEmployeesService), the delegate class was able to find it by using the following line:

this.service = ServiceLocator.getInstance().getHTTPService(
                          'loadEmployeesService' );

If you’d need to call a service implemented as RemoteObject, the delegate would be calling the method getRemoteObject() instead of getHTTPService(). For web services, call the method getWebService().

Those who work with Data Management Services can use Cairngorm’s EnterpriseServiceLocator and its method getDataService().

ServiceLocator can be used not only as a repository of all services, but also as an authorization mechanism that restricts access to certain application services based on specified credentials. See its methods setCredentials() and setRemoteCredentials() for details.

The final portion of the loading employees process goes as follows:

  1. The loadEmployeesService class reads Employees.xml

  2. The delegate gets the result and passes it to the result() method of the Command class (see Example 1-5)

  3. The Command class updates the model.employeeListDP via ModelLocator

  4. The List component on the View gets automatically updated, because it’s bound to model.employeeListDP (see Example 1-8)

Example 1-8. The View: EmployeesList.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*" width="100%"
   horizontalAlign="center">

   <mx:Script>
      <![CDATA[
   import com.adobe.cairngorm.control.CairngormEventDispatcher;
   import com.adobe.cafetownsend.control.AddNewEmployeeEvent;
   import com.adobe.cafetownsend.control.UpdateEmployeeEvent;
   import com.adobe.cafetownsend.control.LogoutEvent;
   import com.adobe.cafetownsend.model.AppModelLocator;

   [Bindable]
   private var model : AppModelLocator = AppModelLocator.getInstance();

   // mutate the add new employee button's click event
   public function addNewEmployee() : void {
      // broadcast a cairngorm event
      var cgEvent : AddNewEmployeeEvent = new AddNewEmployeeEvent();
      CairngormEventDispatcher.getInstance().dispatchEvent( cgEvent );

       //de-select the list item
       clearSelectedEmployee();
   }

   // mutate the List's change event
   public function updateEmployee() : void {
     //broadcast a cairngorm event that contains selectedItem from the List
     var cgEvent : UpdateEmployeeEvent = new UpdateEmployeeEvent(
                                       employees_li.selectedItem );
     CairngormEventDispatcher.getInstance().dispatchEvent( cgEvent );

     // de-select the list item
     clearSelectedEmployee();
   }

   // mutate the logout button's click event
   private function logout() : void {
     // broadcast a cairngorm event
     var cgEvent : LogoutEvent = new LogoutEvent();
     CairngormEventDispatcher.getInstance().dispatchEvent( cgEvent );
   }

   // format the names that are displayed in the List
   public function properName( dpItem : Object ) : String {
     return dpItem.lastname + ", " + dpItem.firstname;
   }

   // de-select any selected List items
  private function clearSelectedEmployee() : void {
      employees_li.selectedIndex = -1;
   }
      ]]>
   </mx:Script>

   <mx:Panel title="Employee List" horizontalCenter="0">
      <mx:HBox paddingTop="25">
      <mx:Button label="Add New Employee" click="addNewEmployee()" />
      <mx:Spacer width="100%" />
      <mx:Button label="Logout" click="logout()" />
      <mx:Spacer width="100%" height="20" />
      </mx:HBox>
      <!-- data provider for the list is an ArrayCollection stored in
      the centralized model locator -->
      <mx:List id="employees_li" dataProvider="{ model.employeeListDP }"
      labelFunction="properName" change="updateEmployee()" width="100%"
      verticalScrollPolicy="auto"/>
   </mx:Panel>
</mx:VBox>

We’re almost there, but let’s not forget about the ModelLocator, the storage of your application’s data. At the time of this writing, the code of the Café Townsend application published at http://cairngormdocs.org still implements the ModelLocator interface, but recently has been renamed IModelLocator.

In Example 1-9 the class AppModelLocator implements IModelLocator.

Example 1-9. The ModelLocator of Café Townsend Cairngorm

package com.adobe.cafetownsend.model {

   import mx.collections.ArrayCollection;
   import com.adobe.cairngorm.model.ModelLocator;
   import com.adobe.cafetownsend.vo.Employee;
   import com.adobe.cafetownsend.vo.User;

    [Bindable]
   public class AppModelLocator implements ModelLocator {

      // this instance stores a static reference to itself
      private static var model : AppModelLocator;

      // available values for the main viewstack
      // defined as constants to help uncover errors at compile time
      public static const EMPLOYEE_LOGIN : Number =   0;
      public static const EMPLOYEE_LIST : Number =   1;
      public static const EMPLOYEE_DETAIL : Number =   2;
      // viewstack starts out on the login screen
      public var viewing : Number = EMPLOYEE_LOGIN;

      // user object contains uid/passwd
      // its value gets set at login and cleared at logout but nothing
      // binds to it or uses it retained since it was used in the
      // original Adobe CafeTownsend example app
      public var user : User;

      // variable to store error messages from the httpservice
      // nothing currently binds to it, but an Alert or the login box
      // could to show startup errors
      public var errorStatus : String;

      // contains the main employee list, which is populated on startup
      // mx:application's creationComplete event is mutated into a
      // cairngorm event that calls the httpservice for the data
      public var employeeListDP : ArrayCollection;

      // temp holding space for employees we're creating or editing
      // this gets copied into or added onto the main employee list
      public var employeeTemp : Employee;

      // singleton: constructor only allows one model locator
      public function AppLocator(){
      if ( AppModelLocator.model != null )
         throw new Error(
         "Only one ModelLocator instance should be instantiated" );
      }

      // singleton always returns the only existing instance to itself
      public static function getInstance() : AppModelLocator {
         if ( model == null )
            model = new AppModelLocator();
         return model;
         }
      }
   }

This model locator stores the data and the state of this application—in particular, the variable employeeListDP, which is the place where the list of employees is being stored.

Please note that as ActionScript 3 does not support private constructors, the public constructor of this class throws an error if someone tries to improperly instantiate it (i.e., using the new command) but the instance of this object already exists.

We went through the entire process of displaying the initial list of employees, but just to ensure that the Cairngorm data flow is clear, we’ll include a brief explanation of yet another use case from Café Townsend.

The user presses the Add New Employee button (see Figure 1-2), enters the detail info for a new employee on the View component shown in Figure 1-3, and presses the Submit button. This is what’s happening between this button click and the moment when the new employee appears in the employee list:

Note

If you want to follow along, please download the source code of Café Townsend and start from EmployeeDetail.mxml on the following line:

<mx:Button label="Submit" click="saveEmployeeEdits()"
id="submit" />
  1. The SaveEmployeeEditsEvent event is dispatched:

    var cgEvent : SaveEmployeeEditsEvent = new
          SaveEmployeeEditsEvent(model.employeeTemp.emp_id, firstname.text,
                 lastname.text,startdate.selectedDate, email.text );
    
    CairngormEventDispatcher.getInstance().dispatchEvent( cgEvent );

    For some reason, the author of this code decided not to use EmployeeVO here and stores each Employee attribute separately in SaveEmployeeEvent. This is not the best way of encapsulating data inside a custom event, but let’s keep the original code intact.

  2. The FrontController receives this event and passes it to the registered command SaveEmployeeEditsCommand (see Example 1-4 earlier) for execution.

  3. The execute() method of SaveEmployeeEditsCommand does not use any delegates, as it just needs to add a newly inserted Employee to the model. Because this application does not save modified data anywhere other than in memory, no other service calls are made to pass the changed data to the server side for persistence.

  4. The View portion of the employee list gets updated automatically as a result of data binding.

While planning for your application with Cairngorm, think of all events, services, value objects, and business services and then create appropriate classes similarly to the way it was done in the Café Townsend example.

To Use or Not to Use Cairngorm?

Online, you may encounter lots of debate regarding whether Cairngorm should be used in Flex projects. With all due respect to the creators of Cairngorm, we don’t believe that Cairngorm makes a Flex team more productive and that most enterprise projects would not benefit from it. We prefer working with frameworks that offer enhanced Flex components rather than just separation of work among team members. If you have to develop a project without experienced Flex developers on your team, however, Cairngorm can give your project a structure that will prevent it from failing.

So, is Cairngorm right for your project? Read Chapters 2, and 6, and then decide whether you prefer working with the components described there or one of the architectural MVC frameworks. Meanwhile, keep these observations about Cairngorm in mind:

  • Cairngorm’s architecture is based on components dispatching events to a global event handler without knowing what the latter will do with them. The problem with this approach is in the global nature of such an event handler. The FrontController object serves as a central registry of all Cairngorm events. Although keeping all application events in one place simplifies their maintenance, it leads to tighter coupling of the application components.

  • Using a centralized ModelLocator also makes multiple components dependent on the knowledge of the properties of the model. If your project will start growing, the ModelLocator may not scale well.

  • Modularizing Flex applications is one of the major ways of minimizing the size of the downloadable Shockwave Flash (SWF) files. The other benefit is reusability of the modules. Now imagine a midsize web application that consists of 10 modules. If this application has been built using Cairngorm, each of these modules becomes dependent on the central FrontController located in the main .swf file.

  • Application developers have to write lots of boilerplate code. For example, you have to create additional event and command classes for every event that can be dispatched in your application. Even in a midsize application this can translate to a hundred or more additional Cairngorm-specific classes. To minimize the amount of manually written code, consider using Cairngen, an open source code generator for Cairngorm. It’s available at http://code.google.com/p/cairngen/.

  • FrontController allows you to map only one command per event, yet your application may need to have several event listeners per command.

  • Even though data binding can help in writing less code, because Cairngorm enforces data binding as the only mechanism of updating the views, it makes them nonreusable. For example, you can’t just simply reuse the EmployeeList.mxml from Example 1-8 in another application, because it has an intimate knowledge of the internals of the model and relies on the fact that the model has a public variable employeeListDP. Just simply renaming this variable in the ModelLocator will require changes in one or more views that are bound to it.

  • Having no other choice but data binding for updating the UI may cause performance problems. The global ModelLocator object defines multiple bindable variables representing different models, and the Flex compiler may generate additional EventDispatcher objects on the class level (this depends on the types of the variables). Suppose you have 10 [Bindable] String variables in the ModelLocator. If one of them will get updated, not only will its listener get notified to update the view, but the other 9 will get this event, too.

  • The fact that Cairngorm is built around a Command pattern with a centrally located command repository can be very convenient for some projects that require audit or undo functionality. Every command arrives at the same place, and you can conditionally hook up, say, an undo module that remembers old/new states of some data or logs every user request (this can be a must in some financial trading applications).

  • Cairngorm has been around longer than any other Flex framework. As of today, it’s the most popular framework, and many Flex developers around the world already know it, which may be an important factor for development managers who put together large project teams, especially when the teams consist of a large number of junior Flex developers.

Report Card: Cairngorm

Cairngorm separates business- and UI-related work into different layers, which means that the work of the project team can be split between developers responsible for the visual portion and those who are coding just the business logic of the application. The fact that all services are located in a central place allows us to quickly reconfigure the data sources, i.e., switch to quality assurance (QA) or production servers.

Development managers who have to work with distributed teams of beginner or mid-level Flex developers and need a safety net to split the project work into smaller controllable tasks (e.g., John works on the server side and Srinivas works only on the views) may consider using Cairngorm. Here’s the report card followed by more detailed explanations.

The pros are:

  • It’s a popular framework—many Flex developers know it.

  • It allows separate responsibilities of developers.

  • It lowers the requirements for developers’ skillsets.

The cons are:

  • It requires developers to write lots of additional classes, which adds to project timeline.

  • It’s built on global singletons, which complicates modularization.

  • It allows only one-to-one mapping between events and commands.

  • The framework design is based on singletons, which leads to tight object coupling.

Get Agile Enterprise Application Development with Flex now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.