This article takes a whistle-stop tour of a modular Cairngorm application to demonstrate the architecture in action. Please take the tutorial for a simple Cairngorm application before reading this.
The example shown was part of the presentation "Using Flex Frameworks to build Data Driven Applications" at the MAX 2009 conference. It is now maintained and extended as part of Cairngorm. For more information about this presentation view Christophe Coenraets blog .
This example uses the Parsley Application framework , however this tutorial doesn't go into the Parsley feature set and instead follows on guidelines that Cairngorm specifies on top of that. Therefore, the conceptual practices used here are applicable to a variety of application frameworks available for the Flash platform today.
The InsyncBasic application only showed one functional area, dealing with contacts. The InsyncModularExtended
sample application focuses on how Cairngorm recommends to deal with additional functional areas such as messaging and expenses and is therefore a logical next step to learn more about Cairngorm. This modular requirement reflects in how projects and packages are organized.
While Cairngorm recommends that code be organized by architectural layer (presentation, application, domain, infrastructure) within a functional area, outside a functional area, code is organized more functionally. InsyncModularExtended
is broken into separate projects, where each project represents a separate functional area, and compiles to its own Flex module.
An additional shell project Insync Modular Extended Shell allows to start the application with loading functional areas into a generalized UI shell application that provides global navigation between functional areas.
An API project Insync Modular Extended API is used to share commonly used objects between functional areas. Care needs to be taken that as little behaviour as possible is shared to minimize dependencies and be more resilient to change.
The projects reflect the grouping by functional areas via its package structure. All objects within the functional area contacts belong to a contacts package, and architectural layers are placed as packages beneath these functional packages.
For more information on functional areas see Creating Functional Areas within Modular Development.
The shell application defines the generalized UI shell and navigation between functional areas. Because functional areas are contained within Flex modules, the shell application loads and unloads these modules leveraging Cairngorm's module library . Concrete module SWF files are references in a context file InsyncContext
as shown in this excerpt:
<!-- Infrastructure -->
<module:ParsleyModuleDescriptor objectId="contacts"
url="../../insync-modularExtended-contacts/bin-debug/ContactsModule.swf"
domain="{ ClassInfo.currentDomain }" />
and loaded with a generalized view component InsyncViewLoader
of ContentViewStack
. Note, that the loading component only needs the IModuleManager interface of the Flex SDK to be injected.
:::xml
<mx:ViewStack xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:core="insync.core.*"
xmlns:spicefactory="http://www.spicefactory.org/parsley">
<fx:Metadata>
[Waypoint]
</fx:Metadata>
<fx:Script>
<![CDATA[
import insync.application.ContentDestination;
import com.adobe.cairngorm.module.IModuleManager;
[Bindable]
[Inject(id="contacts")]
public var contacts:IModuleManager;
[Bindable]
[Inject(id="messageCompose")]
public var messageCompose:IModuleManager;
[Bindable]
[Inject(id="expensesModule")]
public var expensesModule:IModuleManager;
]]>
</fx:Script>
<fx:Declarations>
<spicefactory:Configure/>
</fx:Declarations>
<s:NavigatorContent width="100%"
height="100%"
automationName="{ ContentDestination.CONTACTS }">
<core:InsyncViewLoader moduleId="{ ContentDestination.CONTACTS }"
width="100%"
height="100%"
moduleManager="{ contacts }"/>
</s:NavigatorContent>
<s:NavigatorContent width="100%"
height="100%"
automationName="{ ContentDestination.MESSAGE_COMPOSE }">
<core:InsyncViewLoader moduleId="{ ContentDestination.MESSAGE_COMPOSE }"
width="100%"
height="100%"
moduleManager="{ messageCompose }"/>
</s:NavigatorContent>
<s:NavigatorContent width="100%"
height="100%"
automationName="{ ContentDestination.EXPENSES }">
<core:InsyncViewLoader moduleId="{ ContentDestination.EXPENSES }"
width="100%"
height="100%"
moduleManager="{ expensesModule }"/>
</s:NavigatorContent>
</mx:ViewStack>
The view loader component InsyncViewLoader
allows to specify what happens while the module is loading or if an error occurs during the loading process. InsyncViewLoader
is part of the Insync Modular Extended API project because its behaviour relates not only to the shell application but also to other functional areas that load further modules, and must provide consistency in doing so.
When applications are organized in functional areas teams can develop functional areas in isolation of the shell application, which can reduce development times. The ContactsModuleRig of the Insync Modular Extended Contacts project simulates the shell application in the most simplest form in order to concentrate only on the functional area contacts. This not only reduces compilation and manual testing time but can also increase the number of developers who can productively work on the overall project once a project architecture allows isolated development and manages dependencies between the isolated functional areas.
All Insync Module Extended projects also keep their own unit test suites local to the functional areas they belong to. This keeps the unit test suites fast to execute, an important best practise of agile unit testing .
The test-suite of the Insync Modular Extended Contacts project uses FlexUnit 4. Browse through the ContactsTestRunner
to understand how and where it's used. Notice that unit tests do not test MXMLs or other view components. Cairngorm recommends to extract behaviour of difficult to unit test view components into dedicated objects of the Presentation Model and Domain Model.
Also notice that unit tests test objects in isolation to ensure i.e. granular defect localization and fast test suites. If objects contain dependencies, test doubles are injected and controlled with the mock framework ASMock as shown in i.e. SaveContactCommandTest
. The Insync test suites focus on the behavior of objects instead of the wiring. The ContactsTest class i.e. tests the bounds checking of the Contact domain.
Configuration and wiring are not part of unit tests and can more effectively be covered via functional testing. One area the Insync samples currently don't show is a functional test suite. Functional test suites are highly important and a vital part of any enterprise Flex application.
The InsyncModularExtended projects make use of the Cairngorm Validation library in order to group validators and extract them from particular view components into the client side domain. For more information about domain validation and other features of the Validation library, click here .
An intelligent error handling strategy provides an opportunity for RIAs to shine with i.e. graceful fall-back mechanisms. The Insync samples showcase a generic structure of how errors of remote service calls can be handled. The Cairngorm Integration library offers a Parsley tag to define a global error handler for all RemoteObjects in any module. The InsyncModularExtendedShell project's AlertHandler
class defines this handler that currently only logs the error using Flex SDK logging:
[GlobalRemoteObjectFaultHandler]
public function logError(event:FaultEvent):void
{
LOG.error("A server error occurred. event={0}", event.toString());
}
Additionally to this global error handling on remote service calls, other objects might have the need to perform custom behaviour on remote service errors. The Command tag of the Cairngorm Integration library offers a CommandFault tag to handle a fault in any architectual layers.
For example in the InsyncModularExtendedContacts project needed to display an Alert box once a save operation failed and additionally display a failure message to a particular view component. The SaveContactCommand
declares a CommandFault directly in the Command object.
public function execute(event:ContactEvent):AsyncToken
{
return service.save(event.contact) as AsyncToken;
}
public function result(savedContact:Contact, event:ContactEvent):void
{
cache.updateItem(event.contact, savedContact);
}
public function error():void
{
dispatcher(new AlertEvent("Unable to save the contact."));
}
Its implementation (error method) just dispatches an event, which is handled in the AlertHandler
object that dispatches a global Alert view control.
[MessageHandler]
public function showAlert(event:AlertEvent):void
{
var text:String=event.text;
if (text == "" || text == null)
{
text="An error occurred.";
}
Alert.show(text);
}
This keeps global Alert controls centrally defined and more resilient to changes of i.e. User Experience teams that decide to change the error handling of global Alert controls. It also improves the testability of Commands.
The display of the failure message of a particular view component is separated from the Command and defined only in the view component that needs to know about it, the ContactFormPM
[CommandError(selector="save")]
public function onSaveFault(event:ContactEvent):void
{
status="Unable to save contact.";
}
The concludes the whistle-stop tour of a modular Cairngorm application. Feel free to browse more into the examples and provide feedback here. We indent to continuously increase the feature set of these examples in order to show more realistic examples of enterprise Flex applications using Cairngorm.