Architecting a Flex App
Web developers who take Flex for a spin may initially be confused by the user interface model. Although a typical, servlet-style, request-response model will work in Flex, There Is A Better Way. Thanks to the "[Binding]" tag in the Actionscript language, you can bind your view to your model data so that changes to the model are automatically reflected in the view. The Cairngorm microarchitecture formalizes this approach, and is a great starting point for developers who want to figure out how to "make it all work together". In this post I'll describe how variable binding, feature-driven development, and Cairngorm all work together for NoteTag.
Here's how a typical, non-trivial Flex app might be architected:
Domain
The classes that make up the domain model. In NoteTag, this includes Notes, Tasks, and Subscriptions. (A Subscription is a collection of related Notes or Tasks.)
Model
A Singleton that holds bindable instances of the domain model. In NoteTag, the ModelLocator Singleton holds the user's list of Subscriptions, the user's Connections, the current Subscription, the current Note, etc.
View
The UI components (generally, though not always, MXML files). The UI components that are state-dependent are bound to instance variables in the ModelLocator. Any changes to data in the ModelLocator will cause the UI to automatically update, provided that that data is marked as "[Bindable]". An example in NoteTag is the NoteListView, which presents the list of Notes in the current Subscription. If the current Subscription or any of its Notes change then the NoteListView will automatically update to reflect the changes.
Controller
Infrastructure for implementing features as event-driven Commands. Examples in NoteTag include GetSubscriptionCommand, GetNoteCommand, etc.
Business
Business logic classes perform operations on Domain objects, often making calls to remote services and returning the results asynchronously. The SubscriptionManager class is the entry point for most of NoteTag's business logic.
Service
The services layer, which holds all classes that are used to make remote service (HTTPService, RemoteService, and WebService) calls. NoteTag uses a set of service factory classes, which decouple the configuration of specific HTTP services from the components that make calls to HTTP services.
Most application features touch on some or all of these components. Here's the workflow for a typical feature:
- The View broadcasts an event.
- The singleton Controller receives the event, maps it to its corresponding Command, and executes the Command.
- The Command delegates to the appropriate Business object to perform the business logic.
- The Business object performs the business logic, possibly making one or more asynchronous calls to various Services, and returns the result by dispatching a new event to the Command.
- The Command assigns the result to the singleton Model.
- Any Views that are bound to the data in the singleton Model are automatically updated.
So how would this work for a specific feature? When the user selects a Note from the list of Notes (at the top of the screen, below), the Note is loaded from the appropriate repository (Blogger or TypePad) and displayed in the editor (at the bottom of the screen, below):
1. Broadcasting the Event
When the user clicks on the first Note in the list, NoteListView dispatches an event, as follows:
// NoteListView.mxml
private function getSelectedEntry(event:ListEvent) : void {
var selectedEntry:TagBasedEntry =
TagBasedEntry(currentFeed.entries[event.rowIndex-1]);Application.application.dispatchEvent(
new GetNoteEvent(selectedEntry.metadata,true));
}
2. Responding to the Event
Because NoteTag's Front Controller has registered itself to listen for all Command Events, it will be notified when GetNoteEvent is dispatched:
// NoteTagController.as
public class NoteTagController extends FrontController {
public function NoteTagController() {
addCommand(LoginEvent.EVENT_LOGIN, LoginCommand);
addCommand(GetNoteEvent.EVENT_GET_NOTE, GetNoteCommand);
addCommand(GetTaskEvent.EVENT_GET_TASK, GetTaskCommand);
addCommand(PostNoteEvent.EVENT_POST_NOTE, PostNoteCommand);
// more commands...
}
}
Cairngorm's FrontController provides the infrastructure for listening for events and responding to them by executing the appropriate command.
3. Executing the Command
To retrieve the Note, NoteTag needs to make a call to the blog server that stores the user's notes. The GetNoteCommand, which implements Cairngorm's Command interface, takes care of this:
// GetNoteCommand.as
internal class GetNoteCommand extends ChainedCommand {
public override function execute(event:CairngormEvent):void {
var initialEvent:GetNoteEvent = GetNoteEvent(event);var subscriptionManager:SubscriptionManager =
ModelLocator.getInstance().subscriptionManager;setNextEventHandler(subscriptionManager,
handleLoadNote,
LoadNoteEvent.EVENT_LOAD_NOTE,
onSubscriptionFault,
SubscriptionFaultEvent.EVENT_SUBSCRIPTION_FAULT);subscriptionManager.loadNote(initialEvent.metadata);
}private function handleLoadNote(event:LoadNoteEvent):void {
// handle result here...
}// ...
}
(You may have noticed that GetNoteCommand extends ChainedCommand -- this class chains asynchronous calls together using the setNextEventHandler method. In another post, I'll go into greater detail on ChainedCommand, and asynchronous responders in general.)
4. Executing the Business Logic
The SubscriptionManager handles the loading of the Note by executing a series of HTTP service calls to the tag server and the blog server. When the note has been loaded, the SubscriptionManager will dispatch a LoadNoteEvent, which will be handled by GetNoteCommand.handleLoadNote (see the next item).
5. Updating the Model
GetNoteCommand responds to the event by assigning the loaded Note to the appropriate data member on the ModelLocator:
// GetNoteCommand.as
internal class GetNoteCommand extends ChainedCommand {
// ...private function handleLoadNote(event:LoadNoteEvent):void {
ModelLocator.getInstance().currentNote = event.note;
}// ...
}
6. Updating the View
Any views that are bound to the ModelLocator's currentNote member will automatically update themselves to reflect the new data. NoteView, the component that's responsible for displaying the Note in an editor, is one such view:
// NoteView.mxml
xmlns:view="com.adobe.kiwi.notetag.view.*"
xmlns="*">
note="{model.currentNote}" />
Every other feature -- publishing a Note, fetching a Subscription, updating a Task -- is implemented with the same Event-to-Command-to-Model-to-View approach. Command-specific Events can be dispatched from multiple contexts, without knowing which Views will be affected. Views can bind to Model changes without knowing where the originating Event was dispatched from. Loose coupling leads to cleaner, more maintainable code.
Nice article! Thank you!
Ved
Posted by: Ved | 26 May 2008 at 11:44
Thank you! Your article explains Cairngorm very well. This structure definitely work for smaller Flex projects, but what about larger projects?
If I have hundreds of Commands and ValueObjects, would it still be correct to put everything into the singleton instance of FrontController and ModelLocator?
Currently, I have it broken down into many controllers (e.g., AdminController, GuestController, etc...).
Thanks again.
Posted by: Tony | 17 July 2008 at 20:35
Hi this seems to be a good article. can you please let me know how to give a hands on start up with it... Any URL suggestions?
Posted by: Taj | 20 November 2008 at 07:14