FLEX: Modular Application Development Using Cairngorm Architecture

  • Sharebar

Over the past few years, Cairngorm architecture has been hugely embraced for developing enterprise Flex applications. While there are a lot of sources available online that demonstrate building flex applications using Cairngorm Architecture, not many serve the cause if you intend to develop Modular Flex Applications using Cairngorm Architecture.

Brief description of Flex Modules:
Flex Modules are code functionality compiled to dynamically-loadable SWF files that can be loaded and unloaded by an application at run-time.
Modules let you split your application into several pieces, or modules. The main application, or loader, can dynamically load other modules that it requires, when it needs them. It does not have to load all modules when it starts, nor does it have to load any modules if the user does not interact with them.


Pictorial description of Cairngorm Architecture:

Cairngorm Architecture

Cairngorm Architecture

Consider the following scenario: Your team is working on a flex project that is part of the bigger picture(a baseline Flex Application). You might develop your project using an 'independent cairngorm architecture(One that does not interact with the bigger picture)'. Concisely, you are developing a flex module that will be called from a flex application.
In such scenarios, if the module does not have much dependency on the parent application then the module-application integration is many a times postponed till the module starts bearing the fruits, thereby delaying the bitter task of merging architectures(This should generally be a task that should be initiated as soon as you analyze the module design).

While merging Flex Modules and Application that are independently designed using Cairngorm Architecture, following classes should be paid attention to and handled as follows:

  • Controller:

'The Controller is the most sophisticated part of the Cairngorm architecture. The Controller layer is implemented as a singleton FrontController. The FrontController instance, which receives every View-generated event, dispatches the events to the assigned Command class based on the event's declared type.'
Modules can use the controller of the base class. In such a case, all commands of the module need to be registered with the main controller at the time of module load, and, should thereby be removed when the module is unloaded.

package xyz
{
import com.adobe.cairngorm.control.FrontController;
import mx.collections.ArrayCollection;
import .......control.*;
import .......commands.*;
import .......CommandDesc;

public class BaseApplicationController extends FrontController
{
public function BaseApplicationController()
{
initialiseCommands();
}

public function initialiseCommands() : void
{
//Add all the application level commands
addCommand( baseEventName1, baseCommandClass1 );
addCommand( baseEventName2, baseCommandClass2 );
addCommand( baseEventName3, baseCommandClass3 );
addCommand( baseEventName4, baseCommandClass4 );

}

//Add all module level commands
public function addCommands(commandList:ArrayCollection):void{
for(var i:int=0;i
var commDes:CommandDesc =commandList.getItemAt(i) as CommandDesc;
this.addCommand(commDes.getEventName(),commDes.getCommand());
}
}

//Remove all module level commands
public function removeCommands(commandList:ArrayCollection):void{
for(var i:int=0;i
var commDes:CommandDesc =commandList.getItemAt(i) as CommandDesc;
this.removeCommand(commDes.getEventName());
}
}

}
}

Where CommandDesc.as contains the module level event name and the corresponding Command class:

package abc
{
	import com.adobe.cairngorm.commands.Command;

	public class CommandDesc
	{
		private var eventName:String;
		private var command:Class;

		public function getEventName():String{
			return eventName;
		}
		public function setEventName(eventName:String):void{
			this.eventName = eventName;
		}
		public function getCommand():Class{
			return command;
		}
		public function setCommand(command:Class):void{
			this.command = command;
		}
	}
}
  • Model:

'In a Cairngorm Model, related data are stored in Value Objects (VOs), while simple variables can be stored as direct properties of the ModelLocator class. A static reference to the ModelLocator singleton instance is used by the View layers to locate the required data.'
The modelLocator directive is such,that, its always better not to merge modelLocator implementations of the module and the application (since modelLocator contain a lot of contextual information)

Following is a change that needs to be incorporated on the module side, as well as the application side:
Since modelLocator is singleton, we should create a modelLocator implementation in the interface gateway(generally compiled as a SWC) that is used for the application-modules interaction. Since this interface is used by the application SWF to communicate with all module's SWF that it needs to use, a singleton modelLocator in the interface is maintainable and can contain various modules and the application specific modelLocator. (I will soon be posting a blog on modular interaction using SWC gateway interfaces).

Interface level ModelLocator that should implement com.adobe.cairngorm.model.ModelLocator:

package externalInterface
{
	import com.adobe.cairngorm.model.ModelLocator;

	public class InterfaceModelLocator implements com.adobe.cairngorm.model.ModelLocator
	{
		public static var interfaceModelLocator:InterfaceModelLocator;
		private var _modelMap:Object = new Object();

		public function addModel(key:String, model:Object):void{
			_modelMap[key] = model;
		}

		public function getModel(key:String):Object{
			return _modelMap[key];
		}

		public function deleteModel(key:String):void{
			_modelMap[key] = null;
		}

		public function InterfaceModelLocator(){
			if(interfaceModelLocator != null){
	         	throw new Error( "Only one ModelLocator instance should be instantiated" );
			}
		}

		public static function getInstance():InterfaceModelLocator{
	      	if ( interfaceModelLocator == null )
	      	{
	      		interfaceModelLocator = new InterfaceModelLocator();
	      	}
	      	return interfaceModelLocator;
		}
	}
}

Application level ModelLocator that should use the interface level ModelLocator:

[Bindable]
public class ApplicationModelLocator extends InterfaceModelLocator
{
private static var modelLocator : ApplicationModelLocator;

public static function getInstance() : ApplicationModelLocator
{
     if ( modelLocator == null )
    {
      modelLocator = new ApplicationModelLocator();
      InterfaceModelLocator.getInstance().addModel("ApplicationLevelModelName",modelLocator);
    }

    return ApplicationModelLocator(
    InterfaceModelLocator.getInstance().getModel("ApplicationLevelModelName"));
}
}

The module level ModelLocator should be coded on similar lines.

  • Styles:

css usage varies from project to project. All CSS related changes of the application and the module could be kept in a single .css file.

  • Command:

'The Command class processes the event by running the Command class' execute() method, which is an ICommand interface method. The event object may include additional data if required by the developer. The execute() method can update the central Model, as well as invoke a Service class which typically involves communication with a remote server. The IResponder interface, which is also implemented by the Command class, includes onResult and onFault methods to handle responses returned from the invoked remote service.'

Commands imbibe business logic, thus should be unique.

  • Module Loader :

MXML residing in the main application and used to load the child module. This is the module wrapper that is called from the main application and handles all the module loading/unloading etc related events.

xml version="1.0" encoding="utf-8"?>
xmlns:mx="http://www.adobe.com/2006/mxml"
	width="400"
	height="300"
	implements="..........ApplicationGateway"
	xmlns:control=".......control.*"
	xmlns:business="......business.*"
	creationComplete="{initApp()}"
	show="{loadModule()}"
	>

	 BaseApplicationController id="baseApplicationController"/>

	<mx:Script>
		<!--[<span class="hiddenSpellError" pre="">CDATA</span>[-->
			import mx.utils.StringUtil;
			import mx.core.Application;
			import mx.collections.ArrayCollection;
			import mx.modules.ModuleManager;
			import mx.managers.PopUpManager;
			import mx.events.CloseEvent;
			import mx.modules.Module;
			import mx.modules.IModuleInfo;
			import mx.containers.TitleWindow;
			import .............SampleGateway;   // SampleGateway is the communication interface
			import mx.events.ModuleEvent;

			protected var _moduleInfo:IModuleInfo;
			public var moduleTitleWindow:ModuleTitleWindow;
		      private var sampleGateway:SampleGateway = null;

		private function initApp():void {
		    _moduleInfo = ModuleManager.getModule("YourModule.swf");
		   _moduleInfo.addEventListener(ModuleEvent.READY, onModuleReady);
		   _moduleInfo.addEventListener(ModuleEvent.UNLOAD, onModuleUnload);
		}

        	private function loadModule():void {
	           _moduleInfo.load();
        	}

	        private function onModuleReady (e:ModuleEvent):void {
	            var moduleInfo:IModuleInfo = e.currentTarget as IModuleInfo;
	            var module:Module =  (moduleInfo.factory.create () as Module)
	            sampleGateway = module as SampleGateway;
	            sampleGateway.setApplicationGateway(this);

//THE NEXT LINE IS A TYPICAL IMPLEMENTATION FOR CREATING STATIC CUSTOM POPUPS
	            moduleTitleWindow = PopUpManager.createPopUp(DisplayObject(Application.application), ModuleTitleWindow, true) as ModuleTitleWindow;
	            moduleTitleWindow.addChild(module);
	            PopUpManager.centerPopUp(moduleTitleWindow);

	            var commandList:ArrayCollection = SampleGateway.getCommands();
//THE NEXT LINE ADDS ALL MODULE COMMANDS TO THE APPLICATION CONTROLLER
	            baseApplicationController.addCommands(commandList);

	       	}

	   		public function unloadModule(event:Event):void {
	    		 var cmdList:ArrayCollection;
	    		sampleGateway.cleanup();
	    		cmdList = sampleGateway.getCommands();
//THE NEXT LINE DELETES ALL MODULE COMMANDS FROM THE APPLICATION CONTROLLER
	    		baseApplicationController.removeCommands(cmdList);
	    		baseApplicationController.removeAllChildren();
	    		_moduleInfo.release();
	    		PopUpManager.removePopUp(moduleTitleWindow);
	    	}

	    	 public function onModuleUnload (e:ModuleEvent) : void {
	        }

	       public function returnToTarget(target:*):void{
	        	unloadModule(target);
	        }

		]]>
	</mx:Script>

</mx:Canvas>
  • Services.mxml:

All remote objects of the application as well as the module should be declared here:

RemoteObject id="applicationRemoteObject"
	destination="applicationRemoteObjectJavaDelegate"
	showBusyCursor="true"
	result="event.token.resultHandler( event );"
	fault="event.token.faultHandler( event );">
 RemoteObject>
 <mx:RemoteObject id="moduleRemoteObject"
	destination="moduleRemoteObjectJavaDelegate"
	showBusyCursor="true"
	result="event.token.resultHandler( event );"
	fault="event.token.faultHandler( event );">
 </mx:RemoteObject>
  • remoting-config.xml:

All destinations pertaining to the application as well as the module level remote objects should be declared here:

<destination id="applicationRemoteObjectJavaDelegate">
	<properties>
		<source>.....ApplicationRemoteObjectJavaDelegate</source>
	</properties>
</destination>
<destination id="moduleRemoteObjectJavaDelegate">
	<properties>
		<source>.....ModuleRemoteObjectJavaDelegate</source>
	</properties>
</destination>

Post the above mentioned considerations, merging Cairngorm specific modules and applications should be a hassle free process......

Happy Flexing !!

Share the bee buzz:
  • Digg
  • del.icio.us
  • Facebook
  • DZone
  • LinkedIn
  • StumbleUpon
  • Technorati
  • Twitter

3 Responses to “FLEX: Modular Application Development Using Cairngorm Architecture”

  1. I try to implement cairngorm in this way in my modular flex application it works but my module are not completely unloaded. My module contains a datagrid and all VO binded in the grid item persist. Any suggestions?

  2. Make sure of the following…
    >If you are using a common singleton ModelLocator for the main application as well as the Module, then clean up the module on module unload.
    >Remove all module specific commands from the controller.
    >Cleaning up the gateway
    >Releasing the IModuleInfo

  3. I would recommend upgrading to Cairngorm 3, and avoid a couple of pitfalls with C2.

    (Adobe is also recommending this)

Leave a Reply