Thoughts on Cairngorm 3 and Application Architecture

ActionScript, AS3Commons, Cairngorm, Design Patterns, Flex, Inversion of Control, Lessons learned, Microsoft, Spring ActionScript 13 Comments »

Cairngorm 3 was recently announced by Tom Sugden and Alex Uhlmann and has now been released in beta on the Adobe Opensource site.

Don’t expect an updated version of the Cairngorm framework as you know it though. Cairngorm 3 is not aiming to be an MVC implementation, and thus moves away from what version 1 and 2 were, but now consists of a set of patterns and practices, together with a series of libraries that can help to solve common problems.

The Patterns & Practices Group at Microsoft have been promoting a similar mindset for quite some time actually: Prism – patterns & practices Composite Application Guidance for WPF

I can only encourage this decision as it is exactly how I personally think about application design and architecture, especially in Flex and AIR applications. This is also what we are trying to do with Spring ActionScript: our main goal is to provide a solid Inversion of Control container that supports multiple configuration options (XML, MXML, metadata driven component scanning, …) and promote it as a foundation to build applications (and frameworks), with or without your favorite MVC framework. Although we are working on a set of base classes that provide infrastructure for your application (under the name MVCS) with Application Events, Controllers, Abstractions for service layers, … by no means do we want to market the Spring ActionScript framework as yet another MVC implementation. (You might wonder why we are calling it MVCS then, and I’m actually wondering the same… I guess marketing and buzzwords in the opensource world are also important. All kidding aside, the name is certainly subject to change).

What’s in a name

Since this is a complete change of direction for the Cairngorm framework (which it actually no longer is) I would have expected a new name. Continuing to use Cairngorm as a name is in my opinion a bad move and will cause major confusion amongst developers and other people involved in the development process. I think the best thing for Adobe, or at least their Technical Services department, would be to let go of the name and choose a new, fresh name that moves away from the past. (Besides that, who can pronounce “Cairngorm”.)

Dependencies

I noticed that some of the modules that Cairngorm 3 provides depend on other libraries/frameworks, and in general the Parsley application framework. While Parsley is certainly a major player amongst the IoC/application frameworks, and I sincerely respect the author’s work, I don’t think this is a good decision. In case you are wondering: Yes, I would say the same thing if they decided to depend on Spring ActionScript.

The usage and choice of a concrete dependency will have consequences for the adoption and integration of the libraries that Cairngorm 3 provides. Think about it: Why would you want to pull in Parsley, perhaps only to use some of its Reflection API, if you are already running on Spring ActionScript or any other IoC container?

We, the Spring ActionScript team, have actually questioned ourselves about this in the past and have therefore decided to move all the common and reusable code from Spring ActionScript into a set of libraries known as the AS3Commons project. In that respect, I’m a bit disappointed that for instance the AS3Commons Reflect library is not used for reflection purposes, since I think it is more abstracted and unintrusive than a subset of an all-encompassing application framework. Perhaps this may be our fault of not promoting the libraries and the project enough. However, I certainly think that AS3Commons could be a wonderful project and would help to provide common libraries not only to Flex and AIR developers, but to ActionScript 3 developers in general, if it were embraced by the community.

Flex and MVC

Given that RIA technolgies are still evolving at a very fast pace, it is really remarkable to see the huge amount of MVC implementations appear. Not specifically aimed at Cairngorm (at least the previous versions), but rather at almost all MVC architectures available for Flex development, my personal feeling and experience is that the use of MVC architectures in the Adobe RIA space is almost a dogmatic thing and is not needed in most cases. The problem is that people just take an MVC framework as it is and implement it in their applications. More than often not questioning whether or not its usage is justified. Things that could easily and cleverly be solved are ripped apart across layers of the architecture, introducing levels of indirection that are in most cases not needed. The only thing they add is complexity and counter-intuitive development practices.

One of the main arguments for using an MVC framework is that the code is “easy to understand”. Of course the code will be easy to understand if you have been developing with the framework of choice for the N-th time or if you have been digging into the code for a serious amount of time, but ask a newcomer to look at the code and try to explain to you what it is actually doing… I think you’ll be surprised by the responses.

I’m not saying that the use of a particular MVC framework is de facto a bad thing, but the “religious” use and the blind adoption and implementation make a framework a killer for your application. I would encourage everyone to start their next project without an MVC framework and just use the Flex framework with a healthy knowledge of design and presentation patterns. And even if you are using an MVC implementation, think about each layer you introduce, why you need it and the pros and cons it brings.

Conclusion

It’s good to see that Adobe is rethinking their approach to RIA architecture and development practices. I also hope that they will be more open to community input and feedback than they were in the past. Although they did several attempts at engaging the community, I don’t think they really succeeded in that. If not open enough, people will just continue to fork the “framework” and provide extensions that will end up in alternative implementations anyway.

I’m looking forward to seeing how all of this evolves.


Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
 

Spring ActionScript: site and documentation update

ActionScript, Air, Flash, Flex, Inversion of Control, Spring ActionScript 11 Comments »

Some quick news about the status of the project.

The Spring ActionScript project has a new home at http://www.springactionscript.org!

We have been updating the documentation extensively and although this is still a work in progress, this should be a valuable resource to get started with the framework. Please see the “Documentation” section for more info and available doc formats.

There are also some neat new features like advanced autowiring and support for custom XML namespaces (available in SVN) that will be distributed with the new 0.8 release, expected to be released by the end of this week.

As usual, we appreciate any feedback. Join our Google developers group or the forum.

Have fun coding!


Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
 

Spring ActionScript 0.7.1 Released

ActionScript, Air, Flash, Flex, Inversion of Control, Spring ActionScript 12 Comments »

Dear Community,

I’m pleased to announce that the Spring ActionScript 0.7.1 release is now available.

Download | API Documentation | Changelog

This is a minor release that fixes 2 blocking issues:
- import nodes were not parsed correctly, which resulted in the imported nodes not being created.
- circular reference error when 2 objects have bidirectional references.

We also added some extra classes to the domain package.

Enjoy!


Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
 

Spring ActionScript 0.7 Released

ActionScript, Flex, Inversion of Control, Spring ActionScript 2 Comments »

Dear Community,

I’m pleased to announce that the Spring ActionScript 0.7 release is now available.

Download | API Documentation | Changelog

This is the first official release after switching from the Prana Framework to Spring ActionScript. Note that this release does not contain the Cairngorm and PureMVC extensions since these will later be released as separate extension libraries to the framework.

Some of the major changes and enhancements include:

Reflection API is now AS3Reflect

The reflection API is now provided as a separate project named AS3Reflect. This was a demand from several developers that wanted to use the reflection classes without having to use the whole Spring ActionScript codebase. For more info, please see http://code.google.com/p/as3reflect/

MVCS Support

Inspired by the MVCS principles that Joe Berkovitz describes, we created some experimental classes to support the MVCS workflow. Combining these principles with the IoC container of Spring ActionScript is actually very powerfull. Especially if you incorporate a presentation pattern like Presentation Model, you get maximum flexibility and simplicity by autowiring presentation models from the application context into the view layer of your application. I really recommend reading the MVCS and Presentation Model/Autowiring parts of a previous blog post I wrote about this.

IoC Container

We have improved many things in the IoC container and added support for new things like parent and dependant objects. We will continue to improve the core container as we work towards a 1.0 release and will add support for new tags and attributes based on community feedback. So be sure to get in touch with and give us your feedback.

If you feel like helping us out, we could certainly use your help. This project is entirely created on a volunteer basis and in our spare (and limited) free time so needless to say that the more people, the faster we can deliver new releases and guarantee the quality of the framework. So if you think you have someting to offer, be it as a developer, tester, documentation writer, sample creator, … please contact me or leave something in the comments. All help is more than welcome.

Enjoy this release and have fun coding!


Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
 

Spring ActionScript at FlexCamp 2008 Belgium

ActionScript, Conferences, Flex, Inversion of Control, Spring ActionScript, Talks 7 Comments »

Here's a rundown of the presentation I did on Spring ActionScript at the FlexCamp in Antwerp, Belgium. In 40 minutes I tried to cover the basic theory on Inversion of Control, Dependency Injection and the Spring ActionScript project in general. We then dove right into some code we've been writing for an enterprise app that extensively uses Spring ActionScript's IoC container.

Spring ActionScript
View SlideShare presentation or Upload your own. (tags: flash air)

Since time was limited (or in case you were not there), I'll run through the key features of the code I ran through and try to give some more explanation.

Targeting BlazeDS via remoting and messaging

Whenever I'm hitting a backend, I try to have the endpoints externally configured. This allows me to switch between different endpoints - different test or production servers for instance - just by specifying the ip and port in an external properties file. This means that you don't have to specify any compiler arguments that point to the services-config.xml or messaging-config.xml files.

You can set up a channelset and a consumer with the following code. Notice the "method-invocation" element that allows you to execute a method on the object you create. This allows us to directly pass in a channel to the channelset. (In case you are interested, the method-invocation element is preprocessed to an instance of MethodInvokingFactoryObject)

CODE:
  1. <object id="channelSet" class="mx.messaging.ChannelSet">
  2.     <method-invocation name="addChannel">
  3.         <arg>
  4.             <object id="streamingAMFChannel" class="mx.messaging.channels.StreamingAMFChannel">
  5.                 <property name="url" value="http://${host}:${port}/${context-root}/messagebroker/streamingamf" />
  6.             </object>
  7.         </arg>
  8.     </method-invocation>
  9. </object>
  10.  
  11. <object id="eventMessageFeedConsumer" class="mx.messaging.Consumer">
  12.     <property name="destination" value="eventMessageFeed" />
  13.     <property name="channelSet" ref="channelSet" />
  14. </object>

Have you noticed the placeholders "${...}" for the host, port and context-root? These are loaded from a properties file that is referenced in the application context file.

Here's the external properties file:

CODE:
  1. host=192.168.2.134
  2. port=8081
  3. context-root=server-web

And here's the import declaration in the application context xml file:

CODE:
  1. <property file="application-context.properties.txt" />

Of course, you can also specify remote objects in the application context. Here's an example:

CODE:
  1. <object id="remoteObject" class="mx.rpc.remoting.mxml.RemoteObject" abstract="true">
  2.     <property name="endpoint" value="http://${host}:${port}/${context-root}/messagebroker/amf" />
  3.     <property name="showBusyCursor" value="true" />
  4. </object>
  5.  
  6. <object id="userRemoteObject" parent="remoteObject">
  7.     <property name="destination" value="userService" />
  8. </object>

Notice that we actually split this up into 2 objects. The first object is an abstract remote object which we use as a parent object for concrete remote objects. Notice the "abstract=true" and the "parent=remoteObject" properties on these objects.

MVCS support

As part of the project we are working on, we also starting working on some experimental MVCS support. If you haven't heard of MVCS, be sure to check out at Joe Berkovitz' blog.

What we provide so far are some interfaces and abstract base classes for services, operations and commands. Let's look at some code for services and operations.

Here's an interface for a user service, which defines a "getAll" method.

CODE:
  1. public interface IUserService {
  2.   function getAll():IAsyncOperation;
  3. }

We now want to create a concrete service for that interface. For instance, this could be a remote object enabled service. For that, we have a abstract remote object service base class.

CODE:
  1. public class UserRemoteObjectService extends AbstractRemoteObjectService implements IUserService {
  2.        
  3.     public function UserRemoteObjectService(remoteObject:RemoteObject) {
  4.         super(remoteObject);
  5.     }
  6.  
  7.     public function getAll():IAsyncOperation {
  8.         var token:AsyncToken = remoteObject.getAll();
  9.         token.addResponder(new Responder(onGetAllResult, onGetAllFault));
  10.         return getOperation("getAll");
  11.     }
  12.        
  13.     private function onGetAllResult(event:ResultEvent):void {
  14.         getOperation("getAll").dispatchEvent(new AsyncOperationResultEvent(event.result));
  15.     }
  16.        
  17.     private function onGetAllFault(event:FaultEvent):void {
  18.         getOperation("getAll").dispatchEvent(new AsyncOperationErrorEvent(event.fault.faultDetail));
  19.     }
  20. }

For testing purposes, we could also create a mock user service:

CODE:
  1. public class UserMockService extends AbstractService implements IUserService {
  2.    
  3.     private static var logger:ILogger = Log.getLogger("UserMockService");
  4.    
  5.     public function UserMockService() {
  6.         logger.warn("Mock user service instantiated.");
  7.     }
  8.        
  9.     public function getAll():IAsyncOperation {
  10.         var users:ArrayCollection = new ArrayCollection();
  11.        
  12.         var user1:User= new User();
  13.         user1.name = "John";
  14.         users.addItem(user1);
  15.            
  16.         var user2:User= new User();
  17.         user2.name = "Peter";
  18.         users.addItem(user2);
  19.            
  20.         setTimeout(onGetUsersResult, Math.random()*3000, users);
  21.           CursorManager.setBusyCursor();
  22.            
  23.         return getOperation("getAll");
  24.     }
  25.        
  26.     private function onGetUsersResult(users:ArrayCollection):void {
  27.         var operation:IAsyncOperation = getOperation("getAll");
  28.         operation.dispatchEvent(new AsyncOperationResultEvent(users));
  29.         CursorManager.removeBusyCursor();
  30.     }
  31. }

We can now define these objects in the application context as follows:

CODE:
  1. <object id="userService" class="UserMockService"/>
  2.  
  3. ... or ...
  4.  
  5. <object id="userService" class="UserRemoteObjectService">
  6.     <constructor-arg>
  7.         <object id="userRemoteObject" parent="remoteObject">
  8.             <property name="destination" value="detectorGroupService" />
  9.         </object>
  10.     </constructor-arg>
  11. </object>

You can now use one of these implementations by calling the getAll() method. Make sure that you type the service to IUserService and not to a concrete implementation:

CODE:
  1. public function getUsers():void {
  2.     var operation:IAsyncOperation = _userService.getAll();
  3.     operation.addEventListener(AsyncOperationEvent.RESULT,
  4.         function(e:AsyncOperationResultEvent):void {
  5.             logger.info("Users loaded successfully.");
  6.             var users:ArrayCollection = ArrayCollection(e.result);
  7.         }
  8.     );
  9.     operation.addEventListener(AsyncOperationEvent.ERROR,
  10.         function(e:AsyncOperationErrorEvent):void {
  11.             logger.error("An error occurred when loading the users: " + e.fault);
  12.         }
  13.     );
  14. }

To implement your own services, you don't necessarely need to extend the AbstractService or AbstractRemoteObjectService class. These base classes just provide you with some convenient methods, like "getOperation" that creates a generic AsyncOperation for you.

You could of course also create an own implementation of the IAsyncOperation interface for your specific needs. For instance, you could create a GetAllUsersOperation that has all the logic for invoking a method on a remote object and that also deals with the result and fault handling of the remote call.

Presentation Model and Autowiring

I mentioned that we were using Presentation Model to seperate our UI state and logic from our views. This has worked surprisingly well for us and it certainly allows us to write better tests (for the presentation model). If you are new to Presentation Model and Presentation Patterns in general, I really recommend taking a look at the writings by Martin Fowler and Paul Williams.

Seperating logic and state from the view is one thing, but of course in order to use this combination you have to provide your view with an instance of a presentation model at some point. It's easy to read a presentation model from an application context, but injecting it is not that easy. Especially if your view component is nested a few levels deep inside other view components, it can be tricky to pass in the instance of the presentation model. We started of by passing in the presentation models from the root of the application through all components until we had finally reached the component that needed the presentation model. Needless to say that this approach required us to write a lot of extra code that really wasn't supposed to exist...

Enter custom metadata and SystemManager! Using the systemManager, you can listen to events that get fired when view components are added to the stage. This was exactly what we needed to know. Combined with a custom metadata element [Autowired], we now support dependencies being injected into views automatically.

Here's how it works. Suppose you have a UserPanel and a UserPanelPresentationModel. In you UserPanel you'll have a reference to that presentation model so you can actually use it in your view component. By adding the extra [Autowired] metadata you can mark a property (the presentation model) as a property that needs to be autowired when the view is created and added to the stage.

In your UserPanel.mxml file, you declare the presentation model as follows.

CODE:
  1. [Bindable]
  2. [Autowired]
  3. public var presentationModel:UserPanelPresentationModel;

Now all we need to do is listen to the ADDED event on the systemManager and write some glue code that injects the presentation models defined in the application context into the view components. This code goes in your main application file.

CODE:
  1. import mx.utils.DescribeTypeCache;
  2. import mx.utils.DescribeTypeCacheRecord;
  3.  
  4. ...
  5.  
  6. systemManager.addEventListener(Event.ADDED, onAdded);
  7.  
  8. ...
  9.  
  10. private function onAdded(event:Event):void {
  11.   var autowiredObject:Object = event.target;
  12.   var typeInfo:DescribeTypeCacheRecord = DescribeTypeCache.describeType(autowiredObject);
  13.  
  14.   for each (var metaDataNode:XML in typeInfo.typeDescription..metadata) {
  15.     if (metaDataNode.attribute("name") == "Autowired") {
  16.       var propertyNode:XML = metaDataNode.parent();
  17.       var property:String = propertyNode.@name.toString();
  18.       var objectName:String = property;
  19.       var autowireByType:Boolean = true;
  20.  
  21.       for each (var arg:XML in metaDataNode.arg) {
  22.         if (arg.attribute("value") == "byName") {
  23.           autowireByType = false;
  24.         }
  25.       }
  26.                        
  27.       if (autowireByType) {
  28.         var clazz:Class = ClassUtils.forName(propertyNode.@type.toString());
  29.         var objectNames:Array = m_applicationContext.getObjectNamesForType(clazz);
  30.         if (objectNames.length == 1) {
  31.           objectName = objectNames[0];
  32.         }
  33.       }
  34.                        
  35.       autowiredObject[property] = m_applicationContext.getObject(objectName);
  36.     }
  37.   }
  38. }

You may have noticed that we do some extra checks on the attributes of the Autowired metadata. This is because the code above support autowiring by type (the default) and autowiring by name. Autowiring by type means that we will look in the context for objects that are of a certain type. If we find one, we will inject it. Autowiring by name means that we will look for objects in the context that have a certain id.

You can set up autowiring by name as follows:

CODE:
  1. [Bindable]
  2. [Autowired(mode="byName")]
  3. public var presentationModel:UserPanelPresentationModel;

Conclusion

I hope you now got a good understanding of some of the key features, best practices and what we are doing with Spring ActionScript in general. Don't hesitate to join our forum or the team if you want to help us out. We are really looking for more people to join, especially for working on samples and documentation.

http://forum.springframework.org/forumdisplay.php?f=60


Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
 
WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS Log in