Cairngorm Guidelines - UFPEcin.ufpe.br/~hama/Arquivos/Cairngorm Guidelines.pdf · Flex SDK in...
Transcript of Cairngorm Guidelines - UFPEcin.ufpe.br/~hama/Arquivos/Cairngorm Guidelines.pdf · Flex SDK in...
Cairngorm Guidelines
This page collects together the guidelines and best practices established by Adobe Technical Services, the Cairngorm
Committee and our partners. It is presented in three parts:
1. Introduction
2. Architecture Guidelines
3. Best Practices
The introduction explains what Cairngorm is and how to begin applying it. The guidelines describe our preferred
client-side architecture in detail. The best practices are simple lists of recommendations, ready to be handed to
development teams.
1. Introduction
Cairngorm covers a lot of ground. These sections introduce the main features and describe how to get started with
Cairngorm before diving into the more detailed guidelines and best-practices.
Getting Started with Cairngorm
Overview of the Cairngorm Architecture
A Simple Sample Application Explained
A Modular Sample Application Explained
2. Architecture Guidelines
Cairngorm recommends an architecture that arranges code into layers with different kinds of responsibilities. The
following guidelines explain the architecture in detail:
Presentation Layer
Application Layer
Domain Layer
Infrastructure Layer
Modular Development
Options in Loose Coupling
Options to Construct, Retrieve and Isolate Objects
3. Best Practices
It's important to make best use of the features provided by the Flex SDK. These best practice documents make that
easier:
Best Practices for Agile Unit Testing
Best Practices for Performance
Best Practices for Logging
Genuinely Reusable Presentation Components
Quality Guidelines
Getting Started With Cairngorm 3
The best way to get started with Cairngorm depends on your own background. Are you a new comer, an old timer or a
visitor?
New Comer - Cairngorm is new to you. How do you even pronounce it?
Old Timer - You're already skilled in Cairngorm 1 & 2, so what's new?
Visitor - You prefer to use another framework, but maybe Cairngorm 3 has something to offer?
New Comer
Named after a mountain range in the eastern Highlands of Scotland, Cairngorm is a resource provided by the Adobe
Technical Services Organisation to developers and technical architects to deliver successful Flex projects in the
enterprise. Cairngorm is pronounced kern-ˌgȯrm and you can hear it said with an American accent here or in more
authentic Scots here.
Cairngorm began life in 2002 as an ActionScript library for building applications that apply a form of the Model-
View-Controller (MVC) pattern. This continued for some time, with the project becoming open-source in 2004. In the
meantime, many third-party frameworks were also developed and released, and a trend emerged towards Inversion-of-
Control (IoC) frameworks. In 2009, the scope of Cairngorm was increased, transforming the MVC framework into a
broader foundation of guidelines, tools and libraries that apply across frameworks.
To get started with Cairngorm, begin on the Cairngorm Guidelines page, where you will find some introductory
material and a sample application. These explain the Cairngorm Architecture, which is our preferred way to structure
Flex applications. Below the introductory material can be found more detailed information about the Cairngorm
Architecture, and a collection of Best Practices documents covering many areas of Flex development. These can be
used to help keep development teams on the right track.
In addition to the guidelines, Cairngorm provides tools and libraries. The tools help to ensure efficient and high-
quality delivery of Flex applications, while the libraries solve recurring problems in Rich Internet Applications.
Old Timer
If you're already skilled in Cairngorm 1 & 2, you'll be interested to hear how the focus of Cairngorm 3 has changed.
Instead of centering around a specific implementation of the Model-View-Controller pattern, Cairngorm 3 consists of
a broader set of guidelines, tools and libraries that apply across frameworks. Cairngorm aims to help developers apply
Flex and third-party frameworks effectively.
The Cairngorm Guidelines describe our preferred client-side architecture, which is known as the Cairngorm
Architecture. It consists of various patterns, some of which will be instantly familiar to experienced Cairngorm users.
For example, the Command pattern is still recommended for encapsulating the operations that an application performs.
Similarly, models and services are declared externally to the view, as with the Service Locator and Model Locator.
However, Cairngorm 3 acknowledges new approaches to accessing models and services, such as using an Inversion-
of-Control (IoC) container to automatically inject them where required.
The original Cairngorm library remains a part of Cairngorm 3, but has not been updated for this release. It can be
found on the Cairngorm Libraries page, alongside a collection of newer libraries. In addition, the Cairngorm Tools
page describes various tools that we have found useful for ensuring quality in the applications we deliver. These range
from commerical products for automation testing, to simple Ant scripts for generating test suites and cleaning up
MXML files.
To migrate from Cairngorm 2 to 3, you should first read the Cairngorm Guidelines to understand how your existing
client-side architecture might be improved. This could involve introducing an inversion-of-control container or simply
refining the way you use the original Cairngorm library. For example, it may be beneficial to decentralize, splitting an
application into distinct functional areas, with their own models and services. After reading the guidelines, you may
evaluate the Cairngorm Tools to decide whether there are parts of your development process that could be
strengthened. Finally, you might review the Cairngorm Libraries to see whether they provide solutions to any of the
problems you are facing in your application code.
Visitor
Thanks for dropping by. If you're already happy delivering Flex projects with one of the newer third-party
frameworks, you might be interested to hear that Cairngorm has changed. Instead of centering around a specific
implementation of the Model-View-Controller (MVC) pattern, Cairngorm has transformed into a foundation of
guidelines, tools and libraries, many of which apply across frameworks.
The Cairngorm Guidelines describe a client-side architecture that can be implemented using any modern inversion-of-
control framework, such as Parsley, Swiz or Spring ActionScript, while the accompanying Best Practices apply to the
Flex SDK in general, regardless of your application framework of choice. These guidelines and best practices can help
teams to delivery high-quality applications.
The Cairngorm Tools page describes a number of tools that we have found valuable during development of large
enterprise projects. These range from commercial products for automation testing, to simple Ant tasks for generating
unit test suites and cleaning up MXML files. The emphasis of the tools is on achieving quality and agility during
delivery.
The Cairngorm Libraries may or may not be of interest. They address common problems that we have encountered
repeatedly in our engagements, such as complex validation requirements, processing tasks sequentially and in parallel,
and managing pop-ups. Some third-party libraries provide their own solutions to these problems. Please evaluate these
libraries and apply them if they solve problems relevant to your project needs.
Overview of the Cairngorm Architecture
Cairngorm provides a reference architecture for Flex applications that is designed to keep things simple and testable. It
describes a way of structuring large applications out of smaller units.
A Cairngorm application consists of:
Architectural Layers that separate classes with different kinds of responsibility
Functional Areas that group classes related to the same area of functionality
Design Patterns that coordinate objects in consistent ways
Architectural Layers
Classes with different kinds of responsibility belong to different layers than can be changed separately from one
another. So if the visual design needs to be tweaked, most changes are confined to the classes in a presentation layer,
and not other parts of the code.
Cairngorm recommends the layers described by Eric Evans in Domain-Driven Design:
Presentation - presents data to the user and gathers input. i.e. the fancy UI
Application - performs the operations of the application. i.e. saving a form
Domain - models the business concerns of the application. i.e. the form data
Infrastructure - coordinates objects and integrates with other systems. i.e. talking to the server
The figure below shows that code in a higher layer makes direct use of code below it. In the other direction,
interactions happen indirectly through event listeners, callback functions and other means.
Continue reading to learn about the relationship between architectural layers, functional areas and design patterns.
Later on, you can refer to the detailed guidelines for each architectural layer.
Functional Areas
Applications are usually composed from distinct functional areas, like a publication editor and a news list. The code
for a functional area should be grouped together and separated from other functional areas, so it can be developed,
tested and profiled independently.
The code for a functional area is contained beneath a single source package. Although multiple functional areas may
be contained within a single Flash Builder project, it is common to extract them into modules or sub-applications,
stored is separate application projects. Shared infrastructure code, such as reusable components and utilities, is best
extracted into library projects.
Functional areas usually contain their own presentation and application layers. They sometimes contain their own
domain models, but other times share some domain knowledge with other functional areas. In that case, the shared
domain models are normally extracted into library projects that functional areas can depend upon.
Functional areas interact with one another through thin interfaces. These typically consist of events and ActionScript
interfaces. Shared infrastructure, such as a messaging framework or service registry is often used to facilitate
interactions.
Functional areas also reuse components, styles and skins for a consistent look and feel and to prevent code
duplication. These are a kind of infrastructure, like the components in the Flex SDK, that should be designed without
knowledge of the particular business domain of an application.
Design Patterns
Design patterns are repeatable solutions to commonly occurring problems in software design. Cairngorm catalogues a
number of design patterns that are effective in Flex. They can be applied within functional areas and across layers to
bring consistency to a large code base. For example, the Command pattern is advocated for encapsulating operations
and the Presentation Model is advocated for removing state and logic from MXML view components.
Conclusion
The Cairngorm architecture recommends composing larger applications from smaller parts, known as functional areas.
It recommends separating code into architectural layers, based on the kind of responsibilities that are carried out. And
it recommends applying consistent design patterns to solve recurring problems and advocates a number of these that
have proven useful in Flex applications.
An analogy for the Cairngorm architecture might be a number of businesses doing work together. Each business
premises is a functional area. The building's exterior and decoration is its presentation layer. Inside there are people
carrying out tasks and they are the application layer. The business that is accomplished – the products created and
sold – is the domain layer, and the building materials, telephones and coffee machines that facilitate the whole
enterprise are the infrastructure layer.
To find out more about the Cairngorm architecture, you can refer to the documentation on the Cairngorm Guidlines
page and the sample applications.
A Simple Sample Application Explained
Introduction
This article takes a whistle-stop tour of a simple Cairngorm application to demonstrate the architecture in action.
The example shown was part of the presentation "Using Flex Frameworks to build Data Driven Applications" at the
MAX 2009 conference. The client side source of the example is now maintained and extended as part of Cairngorm.
For more information about this presentation view Christophe Coenraets blog.
Browse the client side source code here
Server-side component
o Download
o Setup instructions
In order to run the sample application a server side component is required. This is not required to
understand this tutorial.
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 Package Structure
Packages are an important way to organize code. A consistent package structure makes it easier to navigate the code
base, and easier to understand the dependencies between different parts. The InsyncBasic application only shows how
Cairngorm separates code by architectural layer. Behind the insync package, you'll find the following packages,
describing architectural layers:
presentation (concerns visual appearance and presentation behaviour)
application (concerns coordination of presentation and infrastructure components)
domain (soley focused on the problem domain)
infrastructure (concerns i.e. remote services or UI frameworks)
These packages showcase our recommendation for a layered architecture.
Let's dive into these architectural layers starting with the presentation layer.
Look, No Script-block Functions
To begin with, open up the main application file, InsyncBasic.mxml, and navigate down through the view hierarchy.
The first thing you'll notice is an absence of Script-block functions in the MXML view components. This keeps the
views focussed on sizing and layout concerns, making them easier to read and change as the visual design evolves.
Here's the Toolbar component:
<mx:Script>
<![CDATA[
[Inject]
[Bindable]
public var model:ToolbarPM;
]]>
</mx:Script>
<mx:Button
styleName="contactsAddButton" toolTip="Add Contact"
click="model.addContact()"/>
<mx:Spacer width="100%"/>
<mx:Label text="Search:"/>
<mx:TextInput id="searchBox"
change="model.search(searchBox.text)"/>
Notice the Inject tag. This is part of the Parsley Application framework to inject the ToolbarPM into the Toolbar
view.
Enter the Presentation Model
The variables and functions that might have been in the Script-block are instead extracted into presentation models.
Each significant MXML view component has its own presentation model (PM). The PM is focussed on the state and
behaviour required to present data to the user and process user input. It knows nothing about any other presentation
model and it doesn't have knowledge of the application around it.
Here's the ToolbarPM, which trims white-spaces from user input before other client side components receive a search
event.
public class ToolbarPM extends EventDispatcher
{
[MessageDispatcher]
public var dispatcher:Function;
public function addContact():void
{
dispatcher(ContactEvent.newAddContactEvent());
}
public function search(keywords:String):void
{
if (keywords == null)
return;
keywords=StringUtil.trim(keywords);
dispatcher(new SearchEvent(keywords));
}
}
A PM should not reference a view component directly; instead the view observes the PM. The wiring between a view
component and its corresponding PM takes place through binding expressions and in-line event handlers. Here is the
ContactsList view component:
<mx:Script>
<![CDATA[
import insync.domain.Contact;
[Inject]
[Bindable]
public var model:ContactsListPM;
]]>
</mx:Script>
<mx:DataGrid id="list"
width="100%" height="100%"
dataProvider="{ model.contacts.items }" doubleClickEnabled="true"
itemDoubleClick="model.editContact(Contact(list.selectedItem))">
<mx:columns>
<mx:DataGridColumn dataField="firstName"
headerText="First Name"/>
<mx:DataGridColumn dataField="lastName"
headerText="Last Name"/>
<mx:DataGridColumn dataField="phone"
headerText="Phone"/>
</mx:columns>
</mx:DataGrid>
With this approach there is little room for logical errors in the view, and the real logic instead exists in the PM, where
it can easily be unit tested. Unit testing view components directly is more difficult due to the asynchronous component
life-cycle and less dependencies with other components such as other view controls. A PM also simplifies a
component with driving out the used API of a view component from the larger UIComponent API available in
MXML.
Coordinating the Components
The different components of an application need to be coordinated to provide useful features to the user. When
something is typed into the search box, the results need to appear in the contacts list below. This behaviour is
separated from presentation concerns, so the layout can be changed independently.
In the InsyncBasic application, the search operation is invoked by dispatching a SearchEvent from the ToolbarPM:
public function search(keywords:String):void
{
if (keywords == null) return;
keywords = StringUtil.trim(keywords);
if (keywords.length > 0)
{
dispatcher(new SearchEvent(keywords));
}
}
Encapsulate Operations in Commands
The SearchEvent is handled by the application layer, a Command invokes the RPC operation with invoking a remote
service and adding the successful result into a domain object of the domain layer.
public class SearchContactsCommand
{
[Inject]
public var contacts:Contacts;
[Inject]
public var cache:IDataCache;
[Inject]
public var service:RemoteObject;
public function execute(event:SearchEvent):AsyncToken
{
return service.getContactsByName(event.keywords) as AsyncToken;
}
public function result(items:IList):void
{
contacts.addContacts(cache.synchronize(items));
}
}
The Command follows a convention from Parsley's DynamicCommand feature. The request of the SearchEvent is
handled by the method named "execute", while the result is handled by the method named "result". If this example
would need to handle a error response additionally, then another method could have been specified named "error".
This convention is enfored by declaring a DynamicCommand inside a Parsley context.
<DynamicCommand type="{ SearchContactsCommand }"/>
Also note the injected IDataCache utility, which is part of the Cairngorm Integration library. For more information
about IDataCache, click here.
Sequencing Operations
The Insync application refreshes the search view with the latest data once a new contact has been saved. This is easy
to do using the data synchronization feature of LiveCycle Data Services, however using only RPC operations as the
Insync application, the client side needs to manually invoke a search operation after the save operation has returned
successfully. This simple sequencing can be achieved with a dedicated object in the application layer, the
RefreshSearchAfterSaveController.
public class RefreshSearchAfterSaveController
{
[MessageDispatcher]
public var dispatcher:Function;
private var lastSearch:String="";
[MessageHandler(selector="search")]
public function onSearch(event:SearchEvent):void
{
lastSearch=event.keywords;
}
[CommandResult(selector="save")]
public function onSaveComplete():void
{
dispatcher(new SearchEvent(lastSearch));
}
}
For more evolved requirements on sequencing that are often useful for i.e. application start-ups or sequencing of
domain behaviour, check out the Task library.
Keep Your Objects Small
Note the small objects of the previous samples. The RefreshSearchAfterSaveController from above or the
Contacts domain object below only really have one responsiblity. All their methods reference all instance properties
and therefore achieve a high functional cohesion. This keeps your code simple, focused and more resilient to change.
Check out the Single Responsibility Principle for more information.
[Event(name="itemsChange", type="flash.events.Event")]
public class Contacts extends EventDispatcher
{
public static const ITEMS_CHANGE:String = "itemsChange";
private var _items:IList = new ArrayCollection();
[Bindable("itemsChange")]
public function get items():IList
{
return _items;
}
public function addContacts(items:IList):void
{
_items = items;
dispatchEvent(new Event(ITEMS_CHANGE));
}
public function addContact(contact:Contact):int
{
var index:int = -1;
if (items.getItemIndex(contact) == -1)
{
items.addItem(contact);
index = items.length - 1;
}
return index;
}
public function addItemAt(contact:Contact, index:int):void
{
items.addItemAt(contact, index);
}
public function removeContact(contact:Contact):int
{
var index:int = items.getItemIndex(contact);
if (index != -1)
{
items.removeItemAt(index);
}
return index;
}
public function removeContactAt(index:int):Contact
{
return Contact(items.removeItemAt(index));
}
}
This concludes the basic tour through InsyncBasic. InsyncBasic only shows one functional area, dealing with contacts.
The InsyncModularExtended sample application and tutorial 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.
A Modular Sample Application Explained
Introduction
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.
Browse the client side source code here
o InsyncModularExtendedShell
o InsyncModularExtendedAPI
o InsyncModularExtendedContacts
o InsyncModularExtendedMessaging
o InsyncModularExtendedExpenses
Serverside component
o Download
o Setup instructions
In order to run the sample application a server side component is required. This is not required to
understand this tutorial.
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.
Project and Package Structure
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 InsyncModularExtendedShell 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 InsyncModularExtendedAPI 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.
Shell - Module Loading and Navigation
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.
<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 InsyncModularExtendedAPI 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.
Isolated Development and Testing of Functional Areas
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 InsyncModularExtendedContacts
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.
Agile Testing
All InsyncModuleExtended projects also keep their own unit test suites local to the functoinal 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 InsyncModularExtendedContacts 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 behaviour 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.
Domain Validation
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.
Error Handling
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.
Cairngorm Architecture: Presentation Layer
Cairngorm recommends a separation between presentation objects
Presentation Graphic Layer Responsible for visual appearance, layout and effects. Objects often extend flash.display.DisplayObject (i.e.
mx.core.UIComponent) and are often represented in MXML.
Presentation Behavior Layer Responsible for presentation behavior such as communicaton with and between UI controls, formatting, input
validation (validation can also be a Domain Layer responsiblity).
Cairngorm recommends the Presentation Model (PM) as a presentation pattern of the Presentation Behavior Layer,
which encapsulates behaviour and state of the Presentation Graphic Layer while observing it using Flex's data binding.
PMs can ease unit testing of presentation behaviour and clarify the presentation component API of interest.
Applying the Presentation Model: Componentized vs. Hierarchical
Applying the Presentation Model: Coordination with Presenters
Cairngorm Architecture: Application Layer
Coordinates components of other layers and directs to Domain Objects.
Controllers
Cairngorm Architecture: Domain Layer
An abstraction of a real-world problem domain and is there to serve the UI with a representation of the real world.
Responsilbities
Validation
Computations around the problem domain
Filtering (additional filtered collections enrich the Domain Model)
Sorting (can also be Presentation Layer responsibility)
Formatting (can also be a Presentation Layer responsibility)
Cairngorm Architecture: Infrastructure Layer
Support of other layers by providing i.e.
UI framework (Flex framework)
Communication with external environments (See Cairngorm Integration Library)
Change Tracking
Original Value Tracking
Lazy Loading
Caching
Commands
Task framework (See Performing Tasks in Sequence and Parallel)
Modular Development
Modularity
Creating Functional Areas
Using the Cairngorm Module Library
Modular applications and Developing and loading sub-applications of Chaptor 3 Application Architecture
within the Flex 4 Developer Guide
How to Unload Modules Effectively
What is Modularity?
Modularity is something more general than the Flex Module and ModuleLoader components. These are a form of
modularity, but the principle is broader: modularity is about separating applications into smaller units that can be
developed and deployed independently. In the context of Flex, these units might be modules, sub-applications or any
other kind of encapsulated content. A modular application usually has a structure:
In the figure above, the application consists of a thin shell that loads three modules: Dashboard, Contacts and
Messages. These modules represent different functional areas of the application. Their implementation detail is
independent of one another. The application shell is responsible for loading and laying out the modules, and providing
a means of communication between them. This might be a global data model, a registry of interfaces, or in the case of
the illustration above, some kind of message bus.
What are the Benefits of Modularization?
Modularization can bring benefits for end-users of an application and also for the teams that develop and deliver them;
Here are some of the benefits:
A module can be developed, tested and profiled in relative isolation.
Build times are shortened, since changes to one module don't require other modules or the shell application to
be recompiled.
Modules can be loaded on demand, so the initial download for an application is smaller.
If a user never uses the features of one module, that module need not be loaded.
Individual modules can be deployed into production, instead of redeploying an entire application.
Different modules can be loaded for different users based on their entitlements.
A module is easier to understand and maintain than a monolithic application, since it is more functionally
cohesion.
The interactions between modules can be confined to a thin API, reducing regression as an application grows.
The benefits in terms of architecture and development efficiency are arguably the most important. Small groups of
developers can work on individual modules. The contracts of communication between modules can be agreed, so the
implementation is free to change and improve without regression.
Cairngorm 3 and Modularization
One of the key messages of the Cairngorm 3 reference architecture is to separate applications into distinct functional
areas that can be developed independently; in other words a modular architecture, where each functional area is a
module. The Cairngorm guidelines recommend that communication between functional areas takes place through a
thin API that might consist of interfaces, events and data transfer objects. This approach minimizes dependencies
between the distinct functional areas of an application. It promotes what Robert Martin terms "good dependencies" in
the direction of stability. More details can be found in the Creating Functional Areas guideline.
Framework Provisions for Modularization
Many Flex frameworks provide features that support modularity. Parsley, Spring ActionScript, Swiz, Potomac,
PureMVC all offer support in varying degrees. The Cairngorm Module Library builds on top of Parsley's support for
modularity.
The Enterprise Solution: Adobe LiveCycle Mosaic ES2
LiveCycle Mosaic ES2 is far more than a modularity framework, but modularity is an important part of its design. It's
a client-and-server-side technology for building applications by composition, combining different pieces into
personalized views that are focussed on the activities that different users perform. On the server-side, reusable
application assets can be stored and shared, while LiveCycle DataServices is available for integrating with different
data sources. On the client-side, applications are assembled from different tiles, that can be developed from scratch or
adapted from existing Flex and HTML applications. A customizable shell application loads and lays out the tiles, and
the framework provides a publish-subscribe messaging API for communication between them. For more details about
the Adobe enterprise solution to modularity, refer to the LiveCycle Mosaic ES2 product page.
Conclusion
As we attempt to build bigger and better applications in Flex and AIR, the topic of modularity becomes more
important to ensure efficient development, scalability and maintainability. It is vital to be able to separate portions of a
large application so they can be developed, tested and deployed independently. The Flex SDK provides some simple
means of modularization with Modules and Sub-Applications, and some frameworks build on top of these to provide
more features. Furthermore, Adobe has now released an enterprise solution in LiveCycle Mosaic ES2 that provides the
infrastructure for rapidly developing and deploying RIAs to the browser and desktop in a modular way.
Options in Loose Coupling
We loosely couple architectural layers because we assume each architectural layer is likely to change for different
reasons. Loose coupling of each architectural layer can improve the resilience to change of each layer. It can also
reduce dependencies of shared objects (or API projects, see Creating Functional Areas) as less dependent objects
exist. However, loose coupling comes at a price. The looser an object is coupled to another object, the higher the cost
of readability. Sometimes, objects are not likely to change and very loose coupling only introduces overhead.
There are options in loose coupling that developers can consider depending on the likelihood of change.
The following explains options in coupling on one example relationship between an object of the presentation layer
(ContactListPM) and a domain layer (Contacts). The ContactListPM references an items property of type
mx.collections.IList on the Contacts domain.
Concrete References to Domain
A concrete reference of one object to another represents no coupling. It is the easiest option to read at the cost of a
lower resilience to change and a higher number of dependent objects when shared.
Furthermore, unit tests cannot substitute the concrete reference with a test double in order to focus the purpose of the
test to the object under test, simulate and test interactions and test difficult-to-test behavior in case the concrete object
is difficult to test. See the Agile Testing paper for more information.
Domain Interface
The domain could implement an interface of its API. Preferable this interface will abstract the domain from the
presentation with i.e. only describing the interaction with the PM. Domain interfaces allow a unit test to substitute the
domain in case that is preferable.
Abstracted Interface
A domain interface could be further abstracted to not describe that the PM interacts with a domain. For example a
ContactListPM might need to read an IList collection of a domain object. Instead of creating a domain interface,
create an abstracted interface that only describes the property of an IList i.e.
public interface IItems
{
function items():IList;
}
Metadata Injection
A metadata tag can be created that injects the items property of the Contacts domain into the PM.
The Cairngorm Observer library offers one example build upon Parsley
[Wire(id="contacts",property="items")]
The "contacts" value is how the Contacts domain is declared in the context.
Presenter
Presenters are part of the presentation layer and take a higher level role than PMs. They can coordinate PMs but also
loosen the coupling of a PM to a domain. A ContactListPresenter could be created that subscribes to a domain event
whenever its items collection is changed. On a change, the presenter would directly update the PM as it contains a
reference. The coupling is not loosened overall, but if the PM is regarded to be reused in a different context, this form
of coupling could be a consideration.
Messaging
A messaging mechanism allows two objects to communicate with each other by only knowing about the shared
messaging mechanism. Various architectural Flex frameworks support a messaging mechanism with different
requirements on each participant to know about the messaging mechanism. Cairngorm 2 requires both participants to
know about a concrete framework component, while Swiz can loosen the framework coupling of the message handler
part with metadata while comletley removing it on the message dispatching part. Parsley messaging either only
requires both participants to know about metadata or specify the coupling externally via a context.
Messaging can achieve a low coupling between objects, often with introducing further dependencies on frameworks,
or the introduction of event objects. Furthermore, the scope of messaging is often global, which can lead to further
complications where and in what order messages are handled.
Options in Loose Coupling
We loosely couple architectural layers because we assume each architectural layer is likely to change for different
reasons. Loose coupling of each architectural layer can improve the resilience to change of each layer. It can also
reduce dependencies of shared objects (or API projects, see Creating Functional Areas) as less dependent objects
exist. However, loose coupling comes at a price. The looser an object is coupled to another object, the higher the cost
of readability. Sometimes, objects are not likely to change and very loose coupling only introduces overhead.
There are options in loose coupling that developers can consider depending on the likelihood of change.
The following explains options in coupling on one example relationship between an object of the presentation layer
(ContactListPM) and a domain layer (Contacts). The ContactListPM references an items property of type
mx.collections.IList on the Contacts domain.
Concrete References to Domain
A concrete reference of one object to another represents no coupling. It is the easiest option to read at the cost of a
lower resilience to change and a higher number of dependent objects when shared.
Furthermore, unit tests cannot substitute the concrete reference with a test double in order to focus the purpose of the
test to the object under test, simulate and test interactions and test difficult-to-test behavior in case the concrete object
is difficult to test. See the Agile Testing paper for more information.
Domain Interface
The domain could implement an interface of its API. Preferable this interface will abstract the domain from the
presentation with i.e. only describing the interaction with the PM. Domain interfaces allow a unit test to substitute the
domain in case that is preferable.
Abstracted Interface
A domain interface could be further abstracted to not describe that the PM interacts with a domain. For example a
ContactListPM might need to read an IList collection of a domain object. Instead of creating a domain interface,
create an abstracted interface that only describes the property of an IList i.e.
public interface IItems
{
function items():IList;
}
Metadata Injection
A metadata tag can be created that injects the items property of the Contacts domain into the PM.
The Cairngorm Observer library offers one example build upon Parsley
[Wire(id="contacts",property="items")]
The "contacts" value is how the Contacts domain is declared in the context.
Presenter
Presenters are part of the presentation layer and take a higher level role than PMs. They can coordinate PMs but also
loosen the coupling of a PM to a domain. A ContactListPresenter could be created that subscribes to a domain event
whenever its items collection is changed. On a change, the presenter would directly update the PM as it contains a
reference. The coupling is not loosened overall, but if the PM is regarded to be reused in a different context, this form
of coupling could be a consideration.
Messaging
A messaging mechanism allows two objects to communicate with each other by only knowing about the shared
messaging mechanism. Various architectural Flex frameworks support a messaging mechanism with different
requirements on each participant to know about the messaging mechanism. Cairngorm 2 requires both participants to
know about a concrete framework component, while Swiz can loosen the framework coupling of the message handler
part with metadata while comletley removing it on the message dispatching part. Parsley messaging either only
requires both participants to know about metadata or specify the coupling externally via a context.
Messaging can achieve a low coupling between objects, often with introducing further dependencies on frameworks,
or the introduction of event objects. Furthermore, the scope of messaging is often global, which can lead to further
complications where and in what order messages are handled.
Agile Unit Testing
This paper explains one of the prime goals of Cairngorm; which is to allow developers to follow agile testing of Flex
RIAs. Agile testing combines engineering with testing activities into one activity and can be expressed by following
Test Driven Development where a developer would write tests first. In particular, this paper focuses on unit testing
where units represent objects or single architectural layers in contrast to functional testing (customer tests) where a
unit represents the complete application (many architectural layers). Agile functional testing is also vitally important
to Flex project but the focus of a separate paper.
Why to Isolate - Focus of Unit Tests
Most objects of a system depend on other objects (depended-on objects). Objects contain other objects and delegate
work to them. When unit testing one object the decision is up to the developer to touch the depended-on object with
the same unit test or substitute the depended-on object with an alternative object (test double).
The motivations to substitute (isolate) depended-on objects with test double are manyfold and have to be contrasted
with the motivations against it.
Motivations for isolation
Motivations for isolation (concrete depended-on objects are substituted with test doubles) are:
Simulate hard to test behaviour If the object under test contains behaviour that is hard to simulate in a unit testing environment (i.e. specific
error conditions, or asynchronous events), a substituted test double could simulate it and could allow more test
scenarios and faster test runs.
Granular defect localization If an implementation changes and a test flags up an unexpected result, it is easy to localize the defect if the
object under test is isolated from it's environment.
No triggering of unwanted behaviour Without any isolation, the behaviour of the object under test might trigger behaviour on the depended-on
object that cause other unexpected effects in unit tests. With substituting the depended-on object, the
behaviour the object under test triggers is controlled.
Less setup code to satisfy dependencies of depended-on objects Without any isolation, depended-on objects might require further dependencies in order to execute without
errors. These dependencies would need to be satisfied by the unit test, increasing the knowledge of the unit
test unnecessarily. Would the object under test use test doubles instead of concrete depended-on objects, the
test doubles can be designed not to require further dependencies, reducing setup code and making the test
easier to read and maintain.
Higher coverage through testing interactions In order to test how an object under test interacts with depended-on objects, the unit test needs access to the
depended-on objects. Test doubles can be designed to offer flexibility on how unit tests can observe the
interaction with their object under test (mocks). Depended-on objects are often not designed for it, and
developers are in danger of sacrificing encapsulation of a concrete implementation of a depended-on object.
Code coverage numbers are more significant When concrete depended-on objects are substituted, they don't get coverage through unit tests that focus on
other objects. Code coverage tools can easier identify the need for additional tests of the concrete depended-on
objects when no code coverage on concrete depended-on objects has been registered via other unit tests.
Motivations against isolation
When concrete depended-on objects are executed by the object under test, then isolation is not performed and the
motivations against isolation can be:
Over-specified unit tests hinder refactorings If all interactions with depended-on objects are tested, refactorings on objects under test that change any of the
interactions also need to change the unit tests and this increases the overall effort of refactorings. To counter
this danger, unit tests should only focus to test one piece of behaviour and not repeat tests of other tests and
not test interactions that are not significant.
More difficult to write and read unit tests If the object under test has to create test doubles for all it's depended-on objects, it increases the size of the
unit test and makes it more difficult to read. However, automatic test double creation libraries such as
ASMock ease the time it takes to write test doubles.
Higher code coverage with fewer tests While objects under test might not be able to and want to test all interactions with depended-on objects, they
automatically cover some behaviour of depended-on objects and can achieve a higher code coverage with
fewer tests. However, the higher code coverage through unit tests that triggered code of depended-on objects
might lead to a false sense of security as the focus of the unit test was not on the depended-on object but on
the object under test.
Danger of substituting behaviour to test When isolating too much developers might accidently substitute objects they actually would want to test and
the risk of integration increases. Watch code coverage results to ensure behaviour continuous to be tested.
The more depended-on objects the unit test touches, the more it steers testing efforts away from unit testing towards
functional testing. At some point, different tooling than unit testing frameworks, additional qualified personnel such as
Quality Engineers and separate test runners are more appropriate for complete functional and black box testing (See
Agile Functional Testing).
When to Isolate - Isolation by Architectural Layer
As shown above, motivations to isolate objects exists as motivations against it exists. When should a developer decide
to isolate an object and when not?
As a general rule of thumb, this paper recommends isolating at each client side architectural layer. Presentation layer,
domain layer, application layer and integration layer. Often developers can isolate objects beyond that and prevent the
dangers of over-specified tests that hinder refactorings with focusing each test on a new, small piece of behaviour that
is not repeatedly covered by another test. Also, interactions do not always need to be tested to the full API including
parameters. A test might gain enough realism by just observing one method call that identifies an interaction with a
depended-on object instead of observing all method calls of similar type including full parameter sets. Mock
frameworks in Flex today (i.e. ASMock and mock-as3) allow this flexibility to prevent having to specify every detail.
How to Isolate - Substitution Patterns and Test Doubles
Isolation can be achieved with substituting the depended-on object with an object that is controlled by the unit test
suite; a test double. The test double takes the same interface as the production object but does have a different
implementation, usually much simplified and targeted for a particular test case or suite.
Test doubles can come in many flavours. The most important test doubles are stubs and mocks. A stub directs inputs
into the object under test. A mock object observes outputs of the object under test.
Substituting - Dependency Injection and Dependency Lookup
Two of the most popular approaches to get substituted behaviour into objects under test is dependency injection and
dependency lookup. Dependency injection injects the test double via the API of the object under test. Dependency
lookup retrieves the test double via another object. The other "locator" object should then provide some mechanism to
configure it during unit tests with a test double.
Substituting - Overriding and Overwriting
Substitution by overriding can be achieved with subclassing the depended-on object with an object only used in tests
that provide an alternative implementation for the behaviour not of interest in unit tests of the object under test.
Substitution by overwriting simply sets (overwrites) a depended-on object with a test double. However, this is only
feasible if the depended-on object is exposed via an API of the object under test and typed as an interface or common
base class.
Substituting - Test Hocks
A Test Hock is a control point (i.e. if-statement) within the object under test that decides to behave differently based
on if it's being exercised during unit tests or during production. This can require a known object with global state to
tell objects if they are in a unit tests or not. However, instead of hardcoding a conditional statement in production
code, this could also be done using Flex's conditional compilation. In any case, this approach adds risk, size and
complexity to production code that would not need to be there following the approaches above and should be used
with caution.
How to Test - Applying Testing Knowledge
More information
Unit Test Organization
While there are many solutions to how to organize unit tests, Cairngorm attempts to provide a convention to improve
consistency between projects.
Same Project
Prefer to keep test code in the same project as source code, separated by source paths.
Same Package
Application code and test code are separated within the same project but use the same package. This is possible by
putting them into different source paths. The application code remains in Flash Builder's standard "src" source path,
while the test code is moved to the additional source path "test". This can also make searches for objects easier.
Test Case Naming
Prefer "Test" suffix instead of "Test" prefix. A suffix can ease type-ahead searches via Flash Builder as the test is
immediately alongside the object under test. If a test class becomes too large, analyse if the design of the object under
test can be improved. If not, multiple test classes can be created with the naming "Type_Feature_Test".
Test Method Naming
Self-documenting tests i.e. should or Given/When/Then naming.
http://dannorth.net/introducing-bdd
Summary - Test Infected Teams
Test-infected developers never write their tests days after their code. Test-infected developers want to write tests,
because that's the way they think about software development. They don't want to think otherwise. Test-infected
developers never have excuses not to test. They are never too busy to test, their environments never take up too much
time to create test data, and their customer never complain that testing is too expensive because it takes too much time.
If it is difficult to create an environment of test infected developers,
analyze the design of the application for testable code, prevent repetitive and over-specified tests, keep tests
small, easy and close to the way objects are used in production.
don't attempt to test every single line of code in your Flex application. Realize that functional tests have a role.
Focus your unit tests to test behaviour and not structure and wiring.
ensure libraries, frameworks, API projects, any other code that is shared by multiple developers achieve the
highest coverage and quality of tests.
keep quality of test code as high as application code. Maintain tests with refactorings but realize that it's
sometimes easier to throw away and rewrite tests as it's sometimes easier to throw away and rewrite code.
Best Practices for Performance
Performance issues fall into three main categories:
1. Perceived Performance
2. CPU Utilization
3. Memory Footprint
These guidelines address all three and aim to help developers produce Flex and AIR applications that feel responsive
and make efficient use of system resources. Performance tuning is all about making trade-offs, so these are not black-
and-white instructions, but rather explanations to be considered when optimizing your own applications.
Note that "micro-level" optimizations, such as using bitwise operators to accelerate algorithms are not covered here.
Also, profiling tools are not explained in detail, but it is assumed that readers are familiar with the Flex Builder
Profiler and standard operating system tools, like Activity Monitor and Task Manager. For additional information
about optimization on the Flash Platform, please read http://www.adobe.com/go/optimize
Contents
Contents
1. Perceived Performance
Defer Creation Policies
Create Lightweight Item Renderers
Make Effects Snappy
Parallelize Service Requests
Load Data on Demand
Be Careful with StyleManager
2. CPU Utilization
Minimize Redraw Regions
Minimize Background Processing
Patch UIMovieClip for Stateful Skins
Don't Play Effects Excessively
3. Memory Consumption
Unload Modules Effectively
Remove Singleton Event Listeners
Consider Reusing Popups
Find Memory Leaks by Comparing Instance Counts
Profile The Export Build with Release Player
Conclusion
1. Perceived Performance
Perceived performance is about the way an application feels to users. If the application start-up seems to take a long
time, or performing an action is sluggish, the perceived performance is poor. If the app feels fast and responsive,
perceived performance is good. Sometimes the perceived performance can be improved easily, for example by
changing the duration of an effect, and other times larger changes are required, like deferring the loading of data.
Defer Creation Policies
Defer the creation of view components that are not immediately visible. Creating and rendering large view
components can be expensive, harming perceived performance. Instead, create views as they are needed, taking
advantage of the default creation policies of containers such as the ViewStack and Accordion.
Note that it is occasionally justified to create hidden views up-front, using an =all= creation policy, but this is the
exception to the rule. If a view that is initially hidden participates in a transition, then the perceived performance of the
transition may be improved by creating the view up-front.
Create Lightweight Item Renderers
List-based components like DataGrid, Tree and List create many item renderers up front and redraw them whenever
changes occur in the data provider. For this reason, it is important that item renderers are lightweight in terms of
creation time and redrawing.
For purely textual item renderers, use or extend the default item renderers for the list-based component. i.e.
DataGridItemRenderer, AdvancedDataGridItemRenderer, etc. For more complex item renderers, containing multiple
text fields, graphics and other assets, prefer to extend UIComponent in ActionScript. Make use of lightweight
children, such as TextField and Sprite, instead of Label and Image. Avoid using Containers for item renderers unless
the simplicity outweighs the loss of performance.
Make Effects Snappy
Effects that play too slowly make an application feel slow, particularly when they occur regularly. Fine tune the effect
play speen to make sure the app feels snappy.
Parallelize Service Requests
Applications often perform many different service requests, particularly during start-up, when the user profile and
initial data sets are usually loaded. These service requests are often independent of one another, so can be performed
concurrently. If there is no need to wait for the result of request A before starting request B, then don't wait and start
request B immediately. The ParallelTask feature of the Cairngorm Task library may help you to achieve this.
Load Data on Demand
Load data on demand when it is needed by the user, instead of upfront. For example, load only the summary
information for a collection of data items, then load the detailed data for individual items as the user navigates into
them. For large collections, use paging to fetch only enough data to render the currently visible rows in a list-based
component. Adobe products such as LiveCycle Data Services provide paging and lazy-loading features.
Be Careful with StyleManager
Operations involving the style manager can be expensive, sometimes causing applications to become unresponsive
while style updates are processed. In particular, using the setStyleDeclaration or loadStyleDeclarations
methods with the update parameter set to true will cause an immediate update of all styles in the whole application,
which can be time consuming.
The best practice is the set the update parameter to false whenever you can. For example, if you are loading a mode
and corresponding compiled stylesheet SWF at runtime, don't add the module to the display list until after the style
loading is complete. Then there will be no need to refresh the styles and the update parameter can be set to false.
2. CPU Utilization
A well-implemented Flex application should have low and stable CPU when the application is idle, with short peaks
in CPU during periods of high activity. For example, when viewing a static publication the CPU should remain low
and stable, but when navigating to a new area of the application for the first time, the CPU should be expected to spike
while the new views are created and rendered. The following are guidelines for achieving the expected CPU profile
for a large Flex application.
Minimize Redraw Regions
Use the "Show Redraw Regions" feature of the Flash Debug Player to see how much redrawing is occurring. Optimize
your application so that only the parts of the screen that you need to change are redrawn. Take particular care with:
Animated SFWs - if an animation has been added to the display list, it will continue to cause redrawing, even
when it is not visible. To avoid this, remove animations from the display list when they are not needed.
List-based components - By default the List, DataGrid, AdvancedDataGrid and Tree will be redrawn entirely
whenever a property of an item in a data provider changes. This can be avoided by following the instructions
in this blog post: Optimizing the DataGrid for Frequent Updates
Minimize Background Processing
The amount of ActionScript processing that happens during each frame affects CPU. Most logic in a Flex application
should be short-lived and happen in response to a user gesture. However, sometimes regular processing is performed
in the background, perhaps in response to frame events or timers. Take care to minimize background processing, since
it can accumulate and push up the CPU.
Patch UIMovieClip for Stateful Skins
A good practice for skinning components is to produce stateful vector skins in Flash and export them with the Flex
Component Kit. All skins produced in this way use UIMovieClip as their base class, as described in the Design Spec.
However, pre-Spark versions of UIMovieClip perform sizing calculations on every frame which causes an
accumulation of CPU. For Flex 3.x projects, this can be avoided by extending UIMovieClip and removing the event
handler, as described by Guillaume Malartre. A consequence of his approach is that some skinned components may
need to be explicitly sized.
Don't Play Effects Excessively
Playing effects consumes CPU because of the processing and redrawing that takes place during the effect. In
applications, it is best to use effects occasionally and not frequently or continuously. For example, using effects to
animate prices in a price grid that changes every second is not advisable.
3. Memory Consumption
When a Flex application is running, Flash Player grabs the memory it needs from the operating system. It uses
memory for storing the class definitions and object instances for your application and also for rendering to screen. A
well-implemented Flex application will maintain an acceptable memory footprint by managing its class definitions
and object instances, so garbage collection can take place effectively. The steps below can help to achieve this.
Unload Modules Effectively
For large, modular applications, the lowest memory footprint can be achieved by unloading modules when they are no
longer needed. However, there are some known issues that can prevent modules from unloading effectively. Conduct
memory profiling and follow the advice from the following blog posts to ensure that modules are unloading
effectively:
How to Unload Modules Effectively
Garbage Collection & Memory
Using the Flex Profiler
More On Finding Memory Leaks
What We Know About Unloading Modules
Note that in some cases, the better approach is the load modules once and then leave them in memory. The trade off is
a higher memory footprint but more responsive navigation between modules. Loading once and leaving in memory
also prevents memory leaks to do with loading duplicate modules into memory.
Remove Singleton Event Listeners
A common cause of memory leaks is to attach event listeners to singletons that reference short-lived objects. For
example:
MySingleton.instance.addEventListener("something", somethingHandler);
The code above creates a reference from the singleton to the object with the somethingHandler function. That
prevents the object from being garbage collected until the singleton itself becomes eligible. But since the singleton
instance is stored in a static class variable, it won't be garbage collected until the class definition is garbage collected.
And that won't happen until the application domain containing the class definition is garbage collected! So, use weak
event listeners when listening to singletons or else explicitly remove event listeners afterwards.
Consider Reusing Popups
The Flex PopupManager class is usually used for opening and closing popup windows. It is common to create a new
instance of a popup each time it is opened. This can cause a memory leak when the popup is later closed. Unless all
references to the popup and its children have been removed, the popup will not be eligible for garbage collection. The
next time the popup is shown, a new instance may be created and again never garbage collected.
Avoid popup-related memory leaks by either:
1. Conducting memory profiling to ensure that the popup instances and all their associated objects are effectively
garbage collected.
2. Reuse a single instance of the popup for showing the popup multiple times.
There is a trade-off here, since option 1, when implemented correctly should produce an application with the lowest
memory footprint. However, option 2 will prevent memory leaks and result in faster perceived performance when the
popup is re-opened. The Cairngorm Popup library includes declarative tags for managing popup opening and closing
that support reuse.
Find Memory Leaks by Comparing Instance Counts
The Loitering Objects view provided by Flash Builder Debugger can sometimes contain false positives, because an
application may legitimately have different instances, but the same instance-count, between two memory snapshots. In
these situations, a better methodology for finding memory leaks is to compare the instance counts between two
snapshots. Alex Harui describes this process on his blog post: More On Finding Memory Leaks
Profile The Export Build with Release Player
Beware that the Flash Debug Player consumes more memory when running an application than the release Flash
Player, due to the instrumentation carried out for debugging and profiling. For this reasons, you should export a
release build and profile your application in the release Flash Player using system tools such as Task Manager,
Activity Monitor, Process Explorer, etc. These tools will give an accurate representation of the memory footprint
experienced by users. Revert back to the Flash Builder Profiler to identify memory leaks and performance bottlenecks.
Conclusion
For performance tuning, there are some best-practices that should just be followed as a matter of course, like using
deferred creation policies and developing with a modular architecture. There are others that involve additional effort
and are not always justified, like replacing easy-to-read with cryptic bitwise operations.
When a performance optimization is likely to complicate the code, it's worth considering whether there might be a
more elegant solution. For example, one might be tempted to introduce a pseudo-threading library to simulate
multiple-threads when translating large quantities of data, but the better solution is not to serve the client with a large
quantity of data in the first place. Instead, paging and lazy-loading can be used to serve smaller quantities of data on
demand.
Best Practices for Logging
Introduction
The Flex Logging Framework is easy to learn and flexible to use. It supports many different scenarios, from helping
developers to debug their code, to sending the details of production application errors over the wire to a remote server
for monitoring. To learn the basics of the Logging Framework refer to the latest Flex Developer Guide and the Flex
Logging Framework chapter of Professional Flex 3. This document describes some best practices for applying the
Logging Framework on enterprise projects.
The APIs provided by the Logging Framework are simple, but they need to be used properly to get the best out of
them. If care is not taken, the benefits that logging can provide for debugging and monitoring an application in
production can be lost. Performance problems can even be created. This article provides a set of best practices to keep
the developers in your team on the right track.
Best Practices
The following best practices are covered:
Get Loggers by Class
Declare Loggers as Static Constants
Format Log Statements Consistently
Parameterize Log Statements with Tokens
Use Log Levels to Indicate Severity
Use Log Filters for Focus
Include Categories to Show Class Names
Use Guard Conditions Appropriately
Configure Logging at Runtime
Get Loggers By Class
Use a simple utility method to retrieve the logger for a particular class, instead of passing in the qualified class name
as a string.
Good:
private static const LOG:ILogger = LogUtil.getLogger(MyClass);
Bad:
private static const LOG:ILogger = Log.getLogger("my.package.MyClass");
With the utility method approach, the class name can be refactored without needing to edit the string. Here is an
implementation for the LogUtil.getLogger() method:
public static function getLogger(c:Class):ILogger
{
var className:String =
getQualifiedClassName(c).replace("::", ".")
return Log.getLogger(className);
}
If performance profiling shows this method call to be a performance bottleneck, you may decide to revert to passing in
the class name manually, since this will run a little faster. However, in most cases the convenience and refactor-ability
of the above approach is the best practice.
Declare Loggers as Static Constants
In most cases, log statements apply to a particular class, so a logger should be declared as a static constant and not an
instance variable.
Good:
private static const LOG:ILogger = LogUtil.getLogger(MyClass);
Bad:
private var log:ILogger = LogUtil.getLogger(MyClass);
Format Log Statements Consistently
Log statements should be formatted consistently and not haphazardly. This ensures that logging code looks
professional and improves readability of log files (for humans or machines).
Good:
LOG.error(
"Something bad has happened: event={0}, message={1}",
event.type,
message);
Bad:
LOG.error("----------- SOMETHING BAD HAS HAPPENED! -----------------");
Parameterize Log Statements
Parameterize log statements using the rest parameter and tokens, instead of appending strings manually. This produces
cleaner code and prevents the composite string from being assembled in the event that no log target is registered.
Good:
LOG.debug(
"Something interesting is happening: event={0}, message={1}",
event.type,
message);
Bad:
LOG.debug(
"Something interesting is happening: event=" +
event.type +
", message=" +
message);
Use Log Levels to Indicate Severity
Use the debug/info/warn/error log levels to indicate the severity of the message, instead of emphasizing important
messages with special characters.
Good:
LOG.error("The service has failed and no data is available.");
Bad:
LOG.debug("!!! ERROR !!! The service has failed !!! ERROR !!!");
Use Log Filters for Focus
Set the log filters on your logging targets in order to focus on the logs produced by a certain part of the system. Do not
instead try to make certain log statements stand-out with special characters. Remember to keep the format of log
messages consistent.
Good:
target.filters = [ "my.important.package.MyClass" ];
target.level = LogEventLevel.INFO;
...
LOG.info("My important message");
Bad:
LOG.debug("----------- My really important message! -----------");
LOG.debug("<<<<<<< another super important log! >>>>>>>>");
LOG.debug("************* CAN YOU SEE ME????? ***************");
The trouble with the "special character" approach is that what is important for one developer one day is different to
what is important for another developer on another day. If every developer uses their own notation for making their
logs stand-out, the resulting log file becomes harder to read than when no emphasis has been placed in the text. Log
levels and filters provide a more controllable and consistent mechanism for the same purpose.
Include Categories to Show Class Names
If you want to see the name of the class issuing a log statement, include categories for your log targets. Do not instead
hard-code the name of the class into log statements.
Good:
target.includeCategory = true;
...
LOG.debug("Executing command");
Bad:
LOG.debug("<<< Executing SynchronizationCommand >>>");
The bad practice above is not refactor-safe. If the class is renamed, the message becomes confusing and if categories
are included in the output, part of the message becomes redundant.
Use Guard Conditions Appropriately
Use guard conditions to prevent unnecessary processing where a log statement is expensive or nested within a loop or
iteration. However, do not use guard conditions around every single log statement since this clutters up code.
Decide whether or not the log statement may be expensive or use the profiler to verify this.
Good:
if (Log.isDebug())
{
LOG.debug("Result received: {0}", ObjectUtil.toString(model));
}
for (var i:int = 0; i<10000; i++)
{
if (Log.isDebug())
{
LOG.debug("Blah blah blah: i={0}", i);
}
}
Bad:
LOG.debug("Result received: {0}", ObjectUtil.toString(model));
for (var i:int = 0; i<10000; i++)
{
LOG.debug("Blah blah blah: i={0}", i);
...
}
In the bad practice above, the model will be converted into a string even if there is no registered target for the debug-
level. Similarly, 10,000 log statements will be issues inside the loop regardless of whether or not there is a target
registered.
Configure Logging at Runtime
Configure logging at runtime is a best practice, since it allows developers to change their log filters without rebuilding
and also allows logging to be reconfigured in production without redeploying. Different log settings can even be
applied to different users.
The process for configuring logging at runtime is beyond the scope of this article, however, the Parsley 2 Application
Framework provides a feature for doing precisely that by loading an external XML file that specifies the targets, levels
and filters.
There are also examples available showing how to do the same thing using Spring ActionScript.
Genuinely Reusable Presentation Components
On larger projects and within enterprises, there's often a case for extracting a set of reusable presentation components
into a Flex library project. In theory, the same components can be reused across modules and sub-applications of
multiple Flex or AIR clients, bringing greater consistency and more rapid development. However, in practice there are
some common mistakes that limit the reusability of components. This post explains what makes a component
genuinely reusable and highlights some techniques from the Flex SDK that can be applied to your own components to
make them more reusable.
What Makes a Component Genuinely Reusable?
There are different levels of reusability, but a fully reusable component should be able to render any kind of data. It
should be equally comfortable with an array of basic, dynamic Objects or a collection of concrete Kangaroos. The
Flex DataGrid has this quality:
<mx:DataGrid dataProvider="{ kangaroos }">
<mx:columns>
<mx:DataGridColumn headerText="Name" dataField="name"/>
<mx:DataGridColumn headerText="Weight" labelFunction="calculateWeight"/>
</mx:columns>
</mx:DataGrid>
Notice how the dataField and labelFunction properties tell the component how to get its data from the Kanagoo
objects without imposing any dependency. These are two of the mechanisms available to make a component genuinely
reusable. Even if the developer has no control over the Kangaroo class itself, perhaps it's part of a 3rd-party library,
they can still easily render these objects within a DataGrid.
The Data Interface Anti-Pattern
One common mistake is to insist that the data being rendered by a component implements a specific interface. For
example, consider a DistributionBar component that renders a simple graph, like that shown in Figure 1.
The distribution bar shows a number of regions with different sizes, each containing a label. It's tempting to configure
this using an array of IRegion objects:
public interface IRegion
{
function get label() : String;
function get size() : int;
}
The distribution bar can then extract the size and label information for each region through this interface. The rationale
for this is that the interface decouples the component from the concrete objects it renders. Anything can be rendered so
long as it implements IRegion, but that so long is in fact the design flaw. By imposing the use of the IRegion interface
the reusability is limited. The interface needs to be added to existing model classes before they can be rendered in a
distribution bar, and worse, if the models were produced by another library or separate team, it might not be an option
to change them, so they'd need to be wrapped. For these reasons, the component is not genuinely reusable.
Reusable Components of the Flex SDK
The Flex SDK contains many reusable components and this is achieved by applying a few standard approaches:
1. Data Fields
2. Data Functions
3. Data Descriptors
4. Factory Objects
These approaches are now described and the same techniques can be applied to your own components to make them
reusable.
Data Fields
A data field is a String property that specifies the name of another property. For example, the labelField property of
the ComboBox or the dataField and dataTipField properties of the DataGridColumn:
<mx:ComboBox dataProvider="{ items }" labelField="name"/>
The component implementation uses the data field to read the data values from the items it renders. For example:
for each (var item:Object in dataProvider)
{
var value:Object = item[dataField];
// do something with the value
}
This is a simple approach but it offers great flexibility. The component can render any readable property of any class
of object.
Data Functions
A data function is a property of type Function that is used to specify a reference to another function. For example, the
labelFunction property of the ComboBox or the dataFunction property of the DataGridColumn.
<mx:DataGridColumn headerText="weight" dataFunction="calculateWeight"/>
The component then invokes the data function, typically passing through an item of data as a parameter. For example:
for each (var item:Object in dataProvider)
{
var value:Object = dataFunction(item);
// do something with the value
}
This approach is similar to using a data field, but offers even more flexibility, since the function can perform
calculations or formatting before returning a value to the component for rendering.
Data Descriptors
A data descriptor is an interface through which a component can analyze the items of data it renders. The developer
can then pass their own implementation of the interface to the component in order to configure it. An example can be
seen in the Tree component of the Flex SDK:
<mx:Tree dataProvider="{ items }">
<mx:dataDescriptor><my:MyDataDescriptor/></mx:dataDescriptor>
</mx:Tree>
The tree can then discover characteristics of the data by querying its data descriptor interface. For example:
for each (var item:Object in dataProvider)
{
var isBranch:Boolean = dataDescriptor.isBranch(item, dataProvider);
// do something with the outcome
}
This approach is very powerful, but is only necessary for complex components such as the Tree. The effort of using
the component is more than a simple List or ComboBox, but the component is still completely decoupled from the
data it renders. If a developer needs to render a new class of object in a tree, they typically write a new implementation
of the ITreeDataDescriptor interface.
Factory Objects
A factory object, in terms of component developent, is a property of type IFactory that is used to instantiate children at
runtime. For example, the itemRenderer property of the List and DataGrid or the dropdownFactory property of the
ComboBox.
<mx:List dataProvider="{ items }" itemRenderer="my.package.MyItemRenderer"/>
The component uses the standard IFactory interface of the Flex SDK to create new objects at runtime:
var itemRenderer:Object = itemRenderer.newInstance();
Then it passes data items into the new object through the IDataRenderer interface:
if (itemRenderer is IDataRenderer)
{
IDataRenderer(itemRenderer).data = item;
}
This approach gives great control over the visual appearance of parts of the component. By providing custom item
renderers, completely different results can be achieved. The logic for processing the item of data can be as simple or
complex as needed and it can be encapsulated inside the item renderer class. Having said that, components that use
factories should define sensible default values, so the component is easy to use in simple cases without setting special
factories. This is the case for all the ListBase components, such as DataGrid, which uses the general purpose
DataGridItemRenderer by default.
It's worth noting here that the Flex compiler has a special relationship with properties of type IFactory. When it
notices such a property being set in MXML, it will automatically generate code to convert class names and in-line
components into instances of ClassFactory. This makes the components easier to use, so developers don't usually need
to instantiate class factories manually, but instead just specify a class name or declare an in-line component.
Performance Considerations
Since fully reusable components tend to use mechanisms such as dynamic property lookups and function references,
there are some trade-offs to consider. These techniques are slower than accessing properties of strongly-typed objects
and they're not resiliant to compile-time type checking. However, the advantages of flexibility and reduced
dependencies can outweigh these drawbacks for larger projects and enterprises.
Conclusion
When fully reusable components are needed, it's good to remember the simple rule: a reusable component should be
able to render any kind of data. This is best achieved by following the conventions laid out in the Flex SDK, such as
data fields, data functions, data descriptors and object factories. It's important to resist the urge to introduce new
interfaces that impose unnecessary obligations on users of your component, because to do so limits its reusability.
Quality Guidelines
Prefer small classes with high functional cohesion (every method refers to all properties).
TODO: add a lot more content.