Archive for the ‘ frameworks ’ Category

Cairngorm 3: The cat is out of the bag

Now that I can officially talk about Cairngorm 3, I thought I’d put some of my thoughts down (more for my future reference, but feel free to follow along).

Alex Uhlmann officially announced the release of Cairngorm 3 beta on October 5th, and from my limited access to the documentation and code, I have a few opinions.

Cairngorm 3 != MVC

I think that it is a positive thing that Adobe Consulting decided to take their hat out of the ring of application architecture providers. The next release of Cairngorm is a complete paradigm shift for the project, in that it is – to simplify it a bit – a set of best practices and support libraries that developers can use within an application architecture such as Mate, Swiz or Parsley.

After I learned this fact, I was surprised that they kept the same project name, but it is a strong brand which will retain the current developer base and level of interest.

I also think that it lays the groundwork for a more free-market environment for other application architectures available today, or soon to be available. I know that a large consideration we made at DaVita was that since Cairngorm was produced by Adobe Consulting, that it would always be a strong contender, and most likely the proverbial 400-pound gorilla. Now that there is not an “official” application architecture supported by Adobe, then the weight of the brand is removed from the decision making. Now becomes about who has the architecture that is best implemented, best documented, easiest to integrate, etc.

It’s Smart Business

Looking at it strictly from a consulting business perspective, this approach to application development support is much more sustainable and has higher revenue-generating potential. If a shop chose to implement the Cairngorm 2 architecture, they may require assistance initially to get up to speed but eventually they become highly proficient and that well dries up.

In addition, it limits their business to people who implement Cairngorm 2.

Now that Cairngorm 3 can enhance and extend any architecture available – since it is about applying best practices – their consulting possibilities are now limitless. It doesn’t matter if you choose to use Mate, or PureMVC, because the Cairngorm 3 libraries can enhance them to support enterprise Flex development.

It’s a win-win situation. They get more business and more expertise, which in turn generates more ideas for Cairngorm, and makes their package more useful as time goes on.

Past Imperfect

However, I now wish I could turn back the clock 1 year and change the decision we made to implement Cairngorm 2 architecture. After doing internal testing of the major packages available, Cairngorm was not our main choice from a technical perspective, but was the top contender from the business perspective, and so we bit the bullet and adapted.

At the time, Mate was my choice for our application development efforts, and now we may have the discussion again internally about moving over. I know it won’t happen, for we’ve invested far too much time and effort into adopting Cairngorm, but I at least want to have the conversation.

Exciting Times

Lastly, I will say I am excited about this new direction that Adobe has taken, and we’ve already begun to play around with some of the code in-house. I’ll post reviews once I’ve had a chance to digest most of it.

Cairngorm: Abstract Events Updated

Purpose

The intended purpose for the AbstractEvent class in Cairngorm was three-fold:

  1. Reduce the number of files needed to manage business events by organizing Events into domains
  2. Implement a callback feature so that a view can specify another action to perform upon the successful completion of an event
  3. Make each domain event class as lightweight as possible

Related Posts

Abstract Commands | Abstract Delegates

Concept

To accomplish these goals, each domain event (e.g. ProjectEvent, PaymentEvent, etc.) would be polymorphic (kinda) in that there is no longer just one identifier designated with the EVENT_ID constant. In addition, I wanted to abstract as much functionality and classification as possible into a common class that each domain event would extend.
abstract_event

Lightweight, Polymorphic Event Class

Here’s an example of a domain event. It has three public identifiers – CREATE_PROJECT, LOAD_PROJECTS, DEACTIVATE_PROJECT- and has no logic other than to pass its constructor’s arguments to AbstractEvent.

ProjectEvent.as

package business.events
{
   import com.adobe.cairngorm.control.CairngormEvent;

   public class ProjectEvent extends AbstractEvent
   {
      public static const CREATE_PROJECT:String = "createProject";
      public static const LOAD_PROJECTS:String = "getProjects";
      public static const DEACTIVATE_PROJECT:String = "deactivateProject";

      public function ProjectEvent(type:String, data:Object = null, callbacks:Array=null)
      {
         super(type, data, callback);
      }
   }
}

AbstractEvent Class

The AbstractEvent class itself isn’t much more complicated at all. It simply has three public variables to hold the type of the event, an object containing key/value pairs for the data, and the reference to the callback function in the view (if specified).

AbstractEvent.as

package business.events
{
   import com.adobe.cairngorm.control.CairngormEvent;

   public class AbstractEvent extends CairngormEvent
   {
      public var callbacks:Array = null;

      public function AbstractEvent(type:String, data:Object = null, callbacks:Array=[])
      {
         super(type);

         this.data = data;
         this.callbacks = callbacks;
      }
   }
}

Example View – Projects

Here’s some snippets of code from my project view. One piece of functionality is for a user to disable a selected project from a list in an AdvancedDataGrid. When the user clicks on the “Disable Project” button, the btnDeactivateProject_Click() function fires and creates a new Project event in which the loadProjects() function is specified as the callback function.

Projects.mxml

private function btnDeactivateProject_Click(e:Event):void
{
   var thisProject:Project = gridProjects.selectedItem as Project;
   new ProjectEvent(ProjectEvent.DEACTIVATE_PROJECT,
            {project:thisProject},
            [loadProjects]).dispatch();
}

public function loadProjects():void
{
   new ProjectEvent(ProjectEvent.LOAD_PROJECTS).dispatch();
}

<mx:Button id="btnDeactivateProject"
	click="btnDeactivateProject_Click(event)"
	x="10" y="393"
	label="Deactivate Project"/>

Mapping Events to Commands

Your controller logic doesn’t change. The only difference is that you’re using one domain event, with specified identities, mapped to different Commands.

Controller.as

package business
{
   import business.commands.*;
   import business.events.*;
   import com.adobe.cairngorm.control.FrontController;

   public class Controller extends FrontController
   {
      public function Controller()
      {
         super();
         addCommand(ProjectEvent.LOAD_PROJECTS, LoadProjectsCommand);
         addCommand(ProjectEvent.CREATE_PROJECT, CreateProjectCommand);
         addCommand(ProjectEvent.DEACTIVATE_PROJECT, DeactivateProjectCommand);
      }
   }
}

Cairngorm: Abstract Commands Updated

Purpose

The intended purpose for the AbstractCommand class in Cairngorm is three-fold:

  1. Further implementation of a callback feature – in conjunction with the AbstractEvent – so that a view can specify other actions to perform upon the successful completion of business event
  2. Create a Responder object for the Delegate to use
  3. Make each Command class as lightweight as possible

Related Posts

Batch Commands | Abstract Events | Abstract Delegates

Concept

abstract_command

Standard, Responseful Command

Below is an example Command in my system, it extends AbstractCommand which in turn implements the ICustomCommand interface. To make the Command responseful, an array of functions is passed from the Event to the Command, which will execute them upon success (see more below). Here’s an example call:

new EmployeeEvent(EmployeeEvent.GET_DETAILS,
         {employee_id:params.empID},
         [showEmployeeDetails, updateEmployeeGraph])

To implement a very basic command, it is only required to set the delegate property to a new instance of the appropriate Delegate class, and then invoke the needed method. The AbstractCommand will automatically create a new Responder object and assign it to the delegate.

LoadProjectCommand.as

public class LoadProjectCommand extends AbstractCommand
{
   private var _model:ModelLocator = ModelLocator.getInstance();

   override public function execute():void
   {
      delegate = new ProjectDelegate();
      (delegate as ProjectDelegate).loadProject(invoker.data);
   }

   override public function commandSuccess(event:Event):void
   {
      if (event.result.success)
      {
         _model.project = (event as ResultEvent).result.data;
         notifyInvoker();
      }
   }
}
}

The invoker object is a reference to the original Event that was called, and to get it assigned as a property of the Command, I extended the FrontController class so that it is assigned before the Command is executed.

protected function executeAbstractCommand(event:AbstractEvent):void
{
   var commandRef:Class = getCommand(event.type);
   var commandToExecute:ICustomCommand = new commandRef();

   commandToExecute.invoker = event;
   commandToExecute.execute();
}

AbstractCommand Class

This class is straightforward and handles the tedious aspects of creating a Command. The notifyInvoker() method simply loops through the array of callback functions – if they exist – and runs them.

AbstractCommand.as

public class AbstractCommand
{
   private var _callbacks:Array = null;
   private var _invoker:AbstractEvent = null;
   private var _delegate:AbstractDelegate = null;

   public function AbstractCommand() { }
   public function execute():void { }
   public function commandSuccess(event:Event):void { }
   public function commandFault(event:Event):void { }

   public function set delegate(d:*):void
   {
      _delegate = d;
      _delegate.setResponder(new Responder(commandSuccess, commandFault));
   }

   public function get delegate():*
   {
      return _delegate;
   }

   public function set invoker(e:AbstractEvent):void
   {
      _invoker = e;
      if (e.callbacks != null) _callbacks = e.callbacks;
   }

   public function get invoker():AbstractEvent
   {
      return _invoker;
   }

   public function notifyInvoker(info:Object = null):void
   {
      if (_callbacks != null)
      {
         for (var i:uint = 0; i < _callbacks.length; i++)
         {
            var callback:Function = _callbacks[i];
           (info != null) ? callback.call(this, info) : callback.call(this);
         }
      }
   }
}

ICustomCommand Interface

ICustomCommand.as

package business.commands
{
   import business.events.AbstractEvent;
   import flash.events.Event;

   public interface ICustomCommand
   {
      function commandSuccess(event:Event):void;
      function commandFault(event:Event):void;
      function execute():void;
      function set invoker(event:AbstractEvent):void;
      function set delegate(delegate:*):void;
   }
}

Application Design 201

Granularity is a well-known topic in software development. Another common concept is encapsulation. There’s books, magazine articles, and blog posts about these topics going back decades (well, not the blog posts).

One challenge we’ve had recently is trying to apply these concepts to a Flex application that uses modules. We want a global data model that modules can access, but have no way to access its internal mechanisms (i.e. no public variables).

The Shell

The core idea is that we want a common data model loaded into a basic Flex app – the Shell – which has no functionality, in and of itself, other than to load and unload modules that contain the business functionality.

This common data would then need to be accessible to every module that got loaded into the Shell – regardless of whether the module needed it or not.

module_communication

The Interface

First thing we need is establish an interface that each module can use to identify itself so that when the Shell loads it, the Shell recognizes that it should send its global data model to the module.

People and places have had their names changed to protect their identity…

package com.widget.common.interfaces
{
   public interface ICoolModule
   {
      function getModuleController():Class
      function setGlobalModel(model:Class):void
   }
}

The key method defined for this mechanism is the setGlobalModel() method. This is the method that it called by the Shell (shown below) when it loads a module to ensure that each module has a reference to the global data model.

The CoolModule

I then extended the basic Flex Module class, so that I could implement the ICoolModule interface and provide basic functionality for accessing the global data model

CoolModule.as

package com.widget.common.components
{
   import com.widget.common.interfaces.ICoolModule;

   import mx.core.Singleton;
   import mx.modules.Module;

   public class CoolModule extends Module implements ICoolModule
   {
      [Bindable] public var _globalModel:*;

      public function CoolModule()
      {
         super();
      }

      public function getModuleController():Class
      {
         return null;
      }

      public function setGlobalModel(globalModelLocator:Class):void
      {
          _globalModel = Singleton.getInstance("IModelLocator") as globalModelLocator;
      }
   }
}

Now when I create a new module, it’s a CoolModule instead of the base Module.

Module1.mxml

<component:CoolModule xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:component="com.widget.common.components.*">

Loading the Module

So now that we’ve set up things on the Module end, let’s see how the Shell actually injects its model into each Module. I’ve got a command that handles loading modules with a commandSuccess() method that handles the coupling (significantly stripped down version here just to show the relevent code).

LoadModuleCommand.as

package com.widget.thisApp.business.commands
{
   import com.widget.thisApp.model.ModelLocator;

   public class ModuleLoaderCommand extends AbstractCommand implements ICustomCommand
   {
      ...
      public function commandSuccess(event:Event):void
      {
         if(_component is ICoolModule)
         {
            var coolModule:ICoolModule = _component as ICoolModule;
            ...
            coolModule.setGlobalModel(ModelLocator);
         }
      }
      ...
   }
}

Now, for those who were paying attention, you may have figured out the key to all of this is that the ModelLocator is a singleton. As long as the module gets loaded into the current application and security domain…

module.load(ApplicationDomain.currentDomain, SecurityDomain.currentDomain);

…then it can access the Model singleton.

Conclusion

We’ve found that while module-parent, and module-module, communication is certainly not elegantly built into the Flex framework, it is possible with a little bit of grunt work. I’m hoping that as the Flex SDK matures and becomes more feature-rich that this feature become more robust.

Cairngorm Patterns: Batch Commands

Related Posts

Abstract Commands

Basic Cairngorm Commands

In the base implementation of the Cairngorm micro-architecture, events and commands are mapped on a 1-1 basis. For every invoker (event) you have to specify one command in your controller.

public function Controller()
{
   super();
   addCommand(TeammateEvent.LOAD_TEAMMATES, LoadTeammatesCommand);
   addCommand(EffortEvent.LOAD_TYPES, LoadEffortTypesCommand);
   addCommand(ProjectEvent.LOAD_PROJECTS, LoadProjectsCommand);
   addCommand(TeammateEvent.LOAD_TEAMMATE_ROLES, LoadTeammateRolesCommand);
   ...
}

For most cases, this is perfectly fine, but in a data-heavy model, such as the one being built for my current application, I need to initialize the application and fire off many individual events in order to load my model with base data.

// Load all teammates in the database for display in the reports tab
new TeammateEvent(TeammateEvent.LOAD_TEAMMATES).dispatch();

// Load all effort types
new EffortEvent(EffortEvent.LOAD_TYPES).dispatch();

// Load all projects
new ProjectEvent(ProjectEvent.LOAD_PROJECTS).dispatch();

// Load all roles
new TeammateEvent(TeammateEvent.LOAD_TEAMMATE_ROLES).dispatch();

...

Batch Commands

In order to avoid having to dispatch n events for n commands to be executed, I would rather map 1 event to n commands and have them fired off in sequence. Then, if I need a batch of commands to fire, I only need to dispatch one event, instead of the 4 I used in the example above.

new ApplicationEvent(ApplicationEvent.LOAD_MODEL).dispatch();

Unfortunately, Cairngorm does not allow this, so I had to extend the basic architecture. I created a addCommands() function that maps a single event to multiple commands as an array.

public class Controller extends ExtendedFrontController
{
   public function Controller()
   {
      addCommands(ApplicationEvent.LOAD_MODEL, [LoadTeammatesCommand,
                                LoadEffortTypesCommand,
                                LoadProjectsCommand,
                                LoadTeammateRolesCommand,
                                ...]);
   }
}

Extended Controller Class

Here’s the code that I’ve come up with so far. This is working fine, but there still some things I plan on implementing in the future.

ExtendedFrontController.as

public class ExtendedFrontController extends FrontController
{
   public function ExtendedFrontController()
   {
      super();
   }

   public function addCommands(commandName:String, commandRefs:Array, useWeakReference:Boolean = true):void
   {
      if(commands[ commandName ] != null)
      {
         throw new CairngormError( CairngormMessageCodes.COMMAND_ALREADY_REGISTERED, commandName );
      }

      if (commandRefs == null)
      {
         throw new Error("The commandRefs argument to addCommands() is null");
      }

      commands[commandName] = commandRefs;

      for (var priority:uint = 0; priority < commandRefs.length; priority++)
      {
         CairngormEventDispatcher.getInstance().addEventListener(commandName, executeCommands, false, priority, useWeakReference);
      }
   }

   protected function executeCommands(event:AbstractEvent):void
   {
      var commandsToInitialise:Array = getCommands(event.type);
      var commandRef:Class;

      for (var i:uint = 0; i < commandsToInitialise.length; i++)
      {
         commandRef = commandsToInitialise[i];
         var commandToExecute:ICustomCommand = new commandRef();
         commandToExecute.execute(event);
      }
   }

   protected function getCommands(commandName:String):Array
   {
      var commands:Array = commands[commandName];

      if ( commands == null )
         throw new CairngormError( CairngormMessageCodes.COMMAND_NOT_FOUND, commandName );

      return commands;
   }
}