From Cairngorm to PureMVC : a quick comparison
Note : you can download the Flex Builder's Project Archive at the bottom of this page
UPDATE : be sure to read Cliff Hall's comment at the end of this post !
I finally found some time to give a try to Cliff Hall's PureMVC framework. This piece of code seems to gain more and more popularity these days, some even declared it as the best Flex application framework currently available. So I thought it was definitely worth checking.
My goal here was to port an existing Cairngorm application to PureMVC, in order to make some comparisons between the two frameworks. So please don't consider this post as an in-depth coverage of PureMVC, but more like a quick overview from a Flex Cairngorm developer perspective.
The application being ported is not a real world application. It's a test RemoteObject application I usually generate as a test for FCG. Basically, I call a "Library" service which performs CRUD operations to a "book" SQL table. The Flex application uses this service to show a list of books in a Datagrid. The user can select a Book, which is then displayed in a book form. The user can edit the book, create new books, and delete existing books.

Basics
I should probably start by mentionning that PureMVC is not Flex framework. It's an AS3 framework, which is completely independent from the Flex framework and even from the Flash framework. It implements its own Observer mechanism called Notifications. There is no such thing as a PureMVCEvent extending a flash.events.Event. But, conceptually, you can think of it as events.
The Model layer can broadcast notifications. The View layer can both listen to and broadcast notifications. The controller layer can broadcast notifications, and can also listen to it by executing commands.
Framework initialization and Controller
When you create a PureMVC application, you first create a Facade which handles the communication between the Model, View and Controller parts of the app. It's a very lightweight Singleton which mainly registers Notifications with Commands, much like a Cairngorm FrontController associates CairngormEvents with Commands.
package example { import example.control.*; import org.puremvc.interfaces.IFacade; import org.puremvc.patterns.facade.Facade; public class ApplicationFacade extends Facade implements IFacade { public static const APP_INIT:String = "AppInit"; public static const CREATE_BOOK:String = "createBook"; public static const GET_BOOKS:String = "getBooks"; public static const UPDATE_BOOK:String = "updateBook"; public static const DELETE_BOOK:String = "deleteBook"; public static const BOOK_SELECTED:String = "bookSelected"; public static const BOOK_DELETED:String = "bookDeleted"; public static function getInstance() : ApplicationFacade { if ( instance == null ) instance = new ApplicationFacade( ); return instance as ApplicationFacade; } override protected function initializeController():void { super.initializeController(); registerCommand(APP_INIT, InitAppCommand); registerCommand(CREATE_BOOK, CreateBookCommand); registerCommand(GET_BOOKS, GetBooksCommand); registerCommand(UPDATE_BOOK, UpdateBookCommand); registerCommand(DELETE_BOOK, DeleteBookCommand); } } }
PureMVC Commands are just like Cairngorm Commands. You generally start by registering a "STARTUP" or "INIT" Notification with a "StartUpCommand" which will have to register Proxies and Mediators.
package example.control { import example.ApplicationFacade; import example.model.BookProxy; import example.view.BookFormMediator; import example.view.BooksPanelMediator; import org.puremvc.interfaces.ICommand; import org.puremvc.interfaces.INotification; import org.puremvc.patterns.command.SimpleCommand; public class InitAppCommand extends SimpleCommand implements ICommand { override public function execute(notification:INotification):void { facade.registerProxy( new BookProxy() ); //... var app:PureMVCExample = notification.getBody() as PureMVCExample; facade.registerMediator( new BooksPanelMediator( app.booksPanel ) ); facade.registerMediator( new BookFormMediator( app.bookForm ) ); //... sendNotification(ApplicationFacade.GET_BOOKS); } } }
Your application's root tag only has to instanciate the facade. A Cairngorm application's root tag, by comparison, has to instanciate the Model, the FrontController and the ServiceLocator (when needed).
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="start()" xmlns:ns1="example.view.components.*"> <mx:Script> <![CDATA[ import org.puremvc.patterns.observer.Notification; import example.ApplicationFacade; private var facade:ApplicationFacade = ApplicationFacade.getInstance(); private function start():void { facade.notifyObservers( new Notification( ApplicationFacade.APP_INIT, this ) ); } ]]> </mx:Script> <ns1:BookForm id="bookForm" x="418" y="10"> </ns1:BookForm> <ns1:BooksPanel id="booksPanel" x="10" y="10"> </ns1:BooksPanel> </mx:Application>
Anyway, for me, this is kind of a non-issue since I think decoupling between the root application tag and any framework should be achieved by creating a main View (a Canvas, most of the time), which is the one initializing the framework and which then be added as a child to the application root tag (whether it is mx:Application, mx:WindowedApplication, or mx:Module).
Model
There is no single-class Model such as a Cairngorm ModelLocator. Here, you create Proxies which store the state of the application, but also some logic related to accessing this data. PureMVC Proxies tend to have more responsabilites than a Cairngorm ModelLocator, as they are meant to deal with remote services, for instance.
Of course, calling the actual remote services will happen through Delegates. But still, I felt quite uncomfortable with the idea of calling my business delegate from the Model layer. So I decided to call it from a command, just like I would in a Cairngorm application. While this seems to be "not very PureMVC", Cliff Hall happened to mention that it could also be done that way. And, indeed, I worked fine.
package example.model { import example.model.vo.Book; import mx.collections.ArrayCollection; import org.puremvc.interfaces.IProxy; import org.puremvc.patterns.proxy.Proxy; public class BookProxy extends Proxy implements IProxy { public static const NAME:String = "BookProxy"; public function BookProxy(proxyName:String=null, data:Object=null) { super(NAME, new ArrayCollection() ); } public function get books():ArrayCollection { return data as ArrayCollection ; } public function replaceBook(pNewBook:Book):void { var id:int = pNewBook.idbook; for ( var i:int =0 ; i < books.length ; i++) { var book:Book = books.getItemAt(i) as Book; if( book.idbook == id){ books.setItemAt(pNewBook, i); return ; } } } public function removeBook(pBook:Book):void { var i:int = books.getItemIndex(pBook); if(i!=-1) books.removeItemAt(i); else trace("Book not found") } } }
package example.control { import example.business.LibraryDelegate; import example.model.BookProxy; import mx.controls.Alert; import mx.rpc.IResponder; import mx.rpc.events.FaultEvent; import org.puremvc.interfaces.ICommand; import org.puremvc.interfaces.INotification; import org.puremvc.patterns.command.SimpleCommand; public class GetBooksCommand extends SimpleCommand implements ICommand, IResponder { override public function execute(notification:INotification):void { var delegate:LibraryDelegate = new LibraryDelegate(this); delegate.getBooks(); } public function result(data:Object):void { var bookList:Array = data.result as Array; var bookProxy:BookProxy = facade.retrieveProxy(BookProxy.NAME) as BookProxy; bookProxy.books.source = bookList; } public function fault(info:Object):void { Alert.show((info as FaultEvent).toString()); } } }
View
One thing I think PureMVC is very could at is adressing a very well known Cairngorm issue : letting view components be notified of events happening in our application while maintaining these views completely decoupled from the rest of the application.
PureMVC does it with the use of Mediators. Mediators are classes that have knowledge of both the view it deals with and the facade (ie the rest of the application). It declares its interests towards some of the Notifications of the application. It then handles these notifications in a dedicated method.
The way it does it, though, is not as elegant as you could imagine : only one "handleNotifications" method handles all notifications in a big - and quite ugly - switch statement. Anyway, it works just fine, and it's still, as far as I'm concerned, a big improvement over Cairngorm ViewHelpers I had used so far.
package example.view { import example.ApplicationFacade; import example.model.BookProxy; import example.model.vo.Book; import example.view.components.BookForm; import flash.events.Event; import org.puremvc.interfaces.IMediator; import org.puremvc.interfaces.INotification; import org.puremvc.patterns.mediator.Mediator; public class BookFormMediator extends Mediator implements IMediator { private var bookProxy:BookProxy; public static const NAME:String = 'BookFormMediator'; public function BookFormMediator(viewComponent:Object=null) { super(viewComponent); bookForm.addEventListener( BookForm.EVENT_UPDATE, onUpdate ); } public function get bookForm():BookForm{ return viewComponent as BookForm; } private function onUpdate(pEvt:Event):void { sendNotification(ApplicationFacade.UPDATE_BOOK, bookForm.editedBook); } override public function listNotificationInterests():Array { return [ ApplicationFacade.BOOK_SELECTED, ApplicationFacade.BOOK_DELETED, ]; } override public function handleNotification(notification:INotification):void { switch ( notification.getName() ) { case ApplicationFacade.BOOK_SELECTED: var book:Book = notification.getBody() as Book; bookForm.currentBook = book ; break; case ApplicationFacade.BOOK_DELETED: bookForm.currentBook = null ; break; } } } }
The real problem though, is the idea of having to create one Mediator per view. That could mean a lot of files in a real world application. Perhaps it is possible to factorize them in some way, but I'm not sure. To be sure, this process might be eased by the use of generators.
(early) Conclusion
I must say I've encountered more difficulties than I expected. Some aspects, such as the relationship between the views and Mediators, are more subtle than they seem at first. While it definetly has similarties with cairngorm, the differences are still very important.
- The fact that you cannot use data binding between the model and the views is pretty annoying since - as I understood it - you have manually update the data to keep them in sync.
- There is no ServiceLocator-like tool, so I had to hardcode RemoteObjects by AS in my Delegate.
- The one Mediator per view paradigm could end up being really annoying too.
- Just like CairngormEvent's, Notification's data property is not strongly typed. So I guess we'll have to subclass and cast a lot...
All in all, I found PureMVC very impressive. If I manage to overcome the few issues that still bother me, It might even be a good alternative over Cairngorm for my next applications.
Finally, as you probably guessed, you can expect to see PureMVC generation in future releases of FCG.
You can download the Flex archive here :
| Attachment | Size |
|---|---|
| PureMVCTestApp.png | 29.65 KB |
| PureMVCExample.zip | 21.53 KB |

Anonymous on December 27th 2007
david_deraedt on December 28th 2007
Anonymous on January 10th 2008
Anonymous on August 01st 2008
Anonymous (not verified) on January 07th 2009
david_deraedt on January 08th 2009
Anonymous (not verified) on January 08th 2009
david_deraedt on January 09th 2009
Cialis (not verified) on September 22nd 2009
I have been pondering how to reconcile a Flex dataProvider with a PureMVC proxy object. I have done almost exactly what you described. I was sure that I was doing something "bad" :). Thanks, Derek Basch
Gerrar Tadalafil (not verified) on March 17th 2009
Anonymous (not verified) on November 14th 2010
Got it, I'll send an email to you now, I myself have only learned about this recently so I hope that it wouldn't be too late. calories burned
Anonymous on December 31st 2007
Anonymous on March 07th 2008
Anonymous on April 21st 2008
Anonymous on May 12th 2008
Romain (not verified) on October 29th 2008
serg (not verified) on March 25th 2011
Thanks, David, that was just what I was looking for!