Spring ActionScript at FlexCamp 2008 Belgium

ActionScript, Conferences, Flex, Inversion of Control, Spring ActionScript, Talks 1 Comment »

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. systemManager.addEventListener(Event.ADDED, onAdded);
  2.  
  3. ...
  4.  
  5. private function onAdded(event:Event):void {
  6.   var autowiredObject:Object = event.target;                   
  7.   var type:Type = Type.forInstance(autowiredObject);
  8.                
  9.   for each (var accessor:Accessor in type.accessors) {
  10.     var autowiredMetaData:MetaData = accessor.getMetaData("Autowired");
  11.       if (autowiredMetaData) {
  12.         var objectName:String = accessor.name;
  13.         var autowireByType:Boolean = true;
  14.  
  15.         if (autowiredMetaData.hasArgumentWithKey("mode")) {
  16.           if (autowiredMetaData.getArgument("mode").value == "byName") {
  17.             autowireByType = false;
  18.             if (autowiredMetaData.hasArgumentWithKey("name")) {
  19.               objectName = autowiredMetaData.getArgument("name").value;
  20.             }
  21.           }
  22.         }
  23.                        
  24.         if (autowireByType) {
  25.           var objectNames:Array = m_applicationContext.getObjectNamesForType(accessor.type.clazz);
  26.           if (objectNames.length == 1) {
  27.             objectName = objectNames[0];
  28.           }
  29.         }
  30.                        
  31.         autowiredObject[accessor.name] = m_applicationContext.getObject(objectName);
  32.       }
  33.     }
  34. }

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
 

Prana goes Spring: Introducing Spring ActionScript

ActionScript, Flex, Inversion of Control, Prana, Spring ActionScript 8 Comments »

Dear community,

I'm pleased to introduce a new Spring extension called Spring ActionScript. This extension, formerly known as the Prana Framework, is aimed at bringing Inversion of Control to ActionScript along with several other interesting utilities.

The extension is written in ActionScript 3 and is targeted at Flex, AIR and Flash developers. The IoC container and its XML dialect is heavily influenced by that of Spring so it should be easy for developers familiar with Spring to get started with it.

Spring ActionScript contains the following features:
* Inversion of Control container - XML driven, based on Spring
* Reflection API - providing a decent API around describeType()
* Cairngorm extensions - configurable Service Locator, extended Front Controller with command factories, Command chaining, ...
* PureMVC extensions - bringing Dependency Injection to PureMVC
* several utilities, assertions, etc

We are also working on the following features, all of which are in an experimental phase:
* MVCS support - providing an architectural framework based on Model-View-Controller-Service
* Domain Driven Design utilities - base classes for entities, value objects, enums, repositories and services
* SqlLite database templates
* Aspect Oriented Programming

The project is currently incubated as we work towards a 1.0 release. The latest available (Prana) release is 0.6 and we plan to release a 0.6.1 version in the coming weeks that introduces the switch to Spring ActionScript.

For more info, please see the following links:

* SVN - https://src.springframework.org/svn/se-springactionscript-as
* FishEye - https://fisheye.springframework.org/browse/se-springactionscript-as
* JIRA: http://jira.springframework.org/browse/SESPRINGACTIONSCRIPTAS
* Forum: http://forum.springframework.org/forumdisplay.php?f=60

Thanks to SpringSource and everyone involved in making this happen!

Hope you enjoy this extension!


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

Vote for the Mobile Rock Werchter Guide as an Adobe Max Finalist!

ActionScript, Boulevart, Flash 4 Comments »

Calling out for some support for my co-worker Thomas Joos who has created a Rock Werchter Mobile Guide. If you have a minute, be sure to give him your vote! Congrats Thomas!



Quoting Thomas: "Last week I received an email from Adobe bringing me the wonderful news that our Rock Werchter Mobile Guide was selected as an Award Finalist entry for the Adobe Max Milan 2008 Awards in the ”Develop” category. We are very honoured to be one of the finalists and are happy to see the work we did together with Barefoot is getting a lot of attention and recognition. To give you an idea: the youtube video showcasing our werchter mobile guide running onto the iphone is already been watched more than a 128.000 times! We would like to thank Adobe for this great opportunity and Barefoot for working together on this cool and innovative project. Having Barefoot and their mind-blowing technology as our partner is awesome and we are proud we got this far together, as a team."

The entries can be found at http://max.adobe.com/eu/experience/#?s=5&p=3

A direct link to enter your vote: https://www.adobemaxsubmission.com/viewer/?projectid=626&location=EMEA

For more details and the original post, see http://labs.boulevart.be/index.php/2008/11/24/adobe-max-milan-award-finalist/


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

Prana on The Flex Show and InfoQ

Prana 1 Comment »

For those who missed this, Prana has been covered on The Flex Show and on InfoQ!

More info:
- http://www.theflexshow.com/blog/index.cfm/2008/10/8
- http://www.infoq.com/news/2008/10/prana-framework-actionscript-3


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

Prana, BlazeDS and WebORB

ActionScript, BlazeDS, Prana, WebORB 10 Comments »

By using Prana to create an application context for your Remoting enabled application, you want to achieve two things for maximum flexibility:

  1. don't compile against services-config.xml
  2. don't specify destinations on the client

It was with the second goal that I ran into a little issue lately when I was creating an application context for a project that uses BlazeDS. Untill now, I had mostly been using WebORB .Net for Remoting with Flex/AIR and the nice thing is that it offers you a GenericDestination. (AMFPHP offers that too btw) That way, for simple destinations, you don't actually have to specify any destinations in remoting-config.xml. When configuring remote objects on the client, you can set the "destination" property to "GenericDestination" and specify the "source" property. The "source" property then refers to the fully qualified classname of the service you want to invoke.

Here's an example:

XML:
  1. <object id="myRemoteObject" class="mx.rpc.remoting.mxml.RemoteObject">
  2.   <property name="destination" value="GenericDestination"/>
  3.   <property name="endpoint" value="http://localhost/project/weborb.aspx"/>
  4.   <property name="source" value="com.domain.project.SomeService"/>
  5. </object>

It might be interesting to note that there must be a "GenericDestination" defined on the server. So in remoting-config.xml, you would see a destination defined as:

XML:
  1. <destination id="GenericDestination">
  2.   <properties>
  3.     <source>*</source>
  4.   </properties>
  5. </destination>

Opposed to a concrete defined destination like:

XML:
  1. <destination id="myDestination">
  2.   <properties>
  3.     <source>com.domain.project.SomeService</source>
  4.   </properties>
  5. </destination>

The wildcard (*) character is replaced at runtime with the value you specify in the "source" property of the remote object.

Switching to BlazeDS however, there is no GenericDestination available. Hm, ok then let's add one. So, I copy the GenericDestination from my WebORB remoting-config.xml to my BlazeDS remoting-config.xml, change my remote objects to specify the GenericDestination and add the source attribute:

XML:
  1. <object id="myRemoteObject" class="mx.rpc.remoting.mxml.RemoteObject">
  2.   <property name="destination" value="GenericDestination"/>
  3.   <property name="endpoint" value="http://localhost:8400/project/messagebroker/amf"/>
  4.   <property name="source" value="com.domain.project.SomeService"/>
  5. </object>

BlazeDS will be smart enough to support this...I hope...nope! A ClassNotFoundException is thrown. Apparently BlazeDS is looking for a class named "*" and is unable to handle this.

I might have a look at the BlazeDS sources and try to figure out how this functionality could be added. Or has anyone allready done this, or knows a workaround?


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 Login