The Spring ActionScript Framework — Part 3: Injecting Services (and Mock Services)

In part 1 of this series, we saw how Flex AS allows you to externalize the configuration and the wiring of objects. In part 2, we discussed how you can “autowire” view properties. We intentionally kept the example simplistic by directly injecting the contact RemoteObject into the views. In real life, however, you generally don’t want to tie your views to a specific service implementation… The views should be independent from your data access strategy: RemoteObject, HTTPService, WebService, mock service, etc.

To satisfy this requirement, we will create an IContactService interface, and two classes that implement that interface:

  1. ContactRemoteObjectService uses RemoteObject to access contact data
  2. ContactMockService encapsulates mock data as a way of testing the application without backend data

For the purpose of this sample application, IContactService is defined as follows:

package insync.services
{
import insync.model.Contact;
import mx.rpc.AsyncToken;

public interface IContactService
{
	function getContactsByName(name:String):AsyncToken;

	function save(contact:Contact):AsyncToken;

	function remove(contact:Contact):AsyncToken;
}
}

ContactRemoteObjectService is implemented as follows:

package insync.services
{
import insync.model.Contact;

import mx.controls.Alert;
import mx.rpc.AsyncResponder;
import mx.rpc.AsyncToken;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.remoting.mxml.RemoteObject;

public class ContactRemoteObjectService implements IContactService
{
	private var remoteObject:RemoteObject;

	public function ContactRemoteObjectService(remoteObject:RemoteObject)
	{
    		this.remoteObject = remoteObject;
	}

	public function save(contact:Contact):AsyncToken
	{
		var token:AsyncToken = remoteObject.save(contact);
		token.contact = contact;
        	token.addResponder(new AsyncResponder(save_result, faultHandler));
        	return token;
	}

	public function remove(contact:Contact):AsyncToken
	{
		var token:AsyncToken = remoteObject.remove(contact);
	        token.addResponder(new AsyncResponder(remove_result, faultHandler));
        	return token;
	}

	public function getContactsByName(name:String):AsyncToken
	{
		var token:AsyncToken = remoteObject.getContactsByName(name);
	        token.addResponder(new AsyncResponder(getContactsByName_result, faultHandler));
        	return token;
	}


	/* Result Handlers ---------------------------------------------------------------*/

	private function save_result(event:ResultEvent, token:AsyncToken=null):void
	{
		// For a create operation, assign the generated primary key to the id property
		// of the contact object
		event.token.contact.id = event.result.id;

		// Dispatch an event on the async token. This allows the caller of the method to register as
		// a listener for the result event of the specific method call. (See ContactForm.mxml for an example.
		event.token.dispatchEvent(event);
	}

	private function remove_result(event:ResultEvent, token:AsyncToken=null):void
	{
		event.token.dispatchEvent(event);
	}

	private function getContactsByName_result(event:ResultEvent, token:AsyncToken=null):void
	{
		event.token.dispatchEvent(event);
    	}

	private function faultHandler(event:FaultEvent, token:AsyncToken=null):void
    	{
    		Alert.show(event.fault.faultString + "\n" + event.fault.faultDetail, "Error Invoking RemoteObject");
    	}

}
}

NOTE: There is nothing specific to the Spring AS framework in this class, except that the remoteObject property will be injected by the framework. This simple implementation is tied to the Flex framework and specifically the rpc API: the contract between the service and other components of the application relies on mx.rpc classes (AsyncToken and ResultEvent). You can replace this basic implementation with your own. Spring AS also provides an abstraction API for working with asynchronous method calls that is not tied to the Flex framework (see the AbstractRemoteObjectService class). Spring AS aims to be an AS framework and not specifically a Flex framework.

ContactMockService is implemented as follows:

package insync.services
{
import insync.model.Contact;
import mx.collections.ArrayCollection;
import mx.rpc.AsyncResponder;
import mx.rpc.AsyncToken;
import mx.rpc.events.ResultEvent;

public class ContactMockService extends MockService implements IContactService
{
	private var contacts:ArrayCollection = new ArrayCollection();

	private var nextId:int;

	public function ContactMockService()
	{
		var contact:Contact = new Contact();
		contact.id = 1;
		contact.firstName = "Christophe";
		contact.lastName = "Coenraets";
		contacts.addItem(contact);

		contact = new Contact();
		contact.id = 2;
		contact.firstName = "Lisa";
		contact.lastName = "Taylor";
		contacts.addItem(contact);

		contact = new Contact();
		contact.id = 3;
		contact.firstName = "John";
		contact.lastName = "Smith";
		contacts.addItem(contact);

		nextId = 4;
	}

	public function save(contact:Contact):AsyncToken
	{
		if (contact.id == 0) // New contact
		{
			contact.id = nextId++;
			contacts.addItem(contact);
		}
		var token:AsyncToken = createToken(contact);
        	token.addResponder(new AsyncResponder(resultHandler, null));
        	return token;
	}

	public function remove(contact:Contact):AsyncToken
	{
		var result:ArrayCollection = new ArrayCollection();
		for (var i:int=0; i<contacts.length; i++)
		{
			var current:Contact = contacts.getItemAt(i) as Contact;
			if (current.id == contact.id)
			{
				contacts.removeItemAt(i);
				break;
			}
		}
		var token:AsyncToken = createToken(contacts);
	        token.addResponder(new AsyncResponder(resultHandler, null));
        	return token;
	}

	public function getContactsByName(name:String):AsyncToken
	{
		var result:ArrayCollection = new ArrayCollection();
		for (var i:int=0; i<contacts.length; i++)
		{
			var contact:Contact = contacts.getItemAt(i) as Contact;
			if (contact.fullName.indexOf(name)>=0)
			{
				result.addItem(contact);
			}
		}

		var token:AsyncToken = createToken(result);
	        token.addResponder(new AsyncResponder(resultHandler, null));
        	return token;
	}

	private function resultHandler(event:ResultEvent, token:AsyncToken = null):void
	{
		event.token.dispatchEvent(event);
	}

}
}

To keep the views independent from a specific implementation of the service, we provide them with a property of the interface type (IContactService). Depending on the Spring AS application context, a specific implementation of the service is injected. For example, ContactForm is defined as follows:

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:controls="insync.controls.*"
	width="100%" height="100%"
	label="{contact.id>0?contact.fullName:'New Contact'}">

	<mx:Script>
	<![CDATA[

	import mx.rpc.events.ResultEvent;
	import insync.events.ContactEvent;
	import insync.services.IContactService;
	import insync.model.Contact;

	[Autowired]
	public var contactService:IContactService;

	[Bindable]
	public var contact:Contact;

	private function save():void
	{
		contact.firstName = firstName.text;
		contact.lastName = lastName.text;
		contact.email = email.text;
		contact.phone = phone.text;
		contact.address = address.text;
		contact.city = city.text;
		contact.state = state.text;
		contact.zip = zip.text;
		contact.pic = picture.source;

		contactService.save(contact).addEventListener(ResultEvent.RESULT,
			function(event:ResultEvent):void
			{
				// Display a status message. Added here to provide an example where
				// a specific contact view needs to be notified of the success or failure
				// of a service operation.
				status.text = "Contact saved successfully";
				setTimeout(function():void{status.text=""}, 1500);
			});
	}

	private function remove():void
	{
		contactService.remove(contact);
	}

	]]>
	</mx:Script>

	<mx:Form>
		<mx:FormItem label="Id">
			<mx:TextInput text="{contact.id}" enabled="false"/>
		</mx:FormItem>
		<mx:FormItem label="First Name">
			<mx:TextInput id="firstName" text="{contact.firstName}"/>
		</mx:FormItem>
		<mx:FormItem label="Last Name">
			<mx:TextInput id="lastName" text="{contact.lastName}"/>
		</mx:FormItem>
		<mx:FormItem label="Email">
			<mx:TextInput id="email" text="{contact.email}"/>
		</mx:FormItem>
		<mx:FormItem label="Phone">
			<mx:TextInput id="phone" text="{contact.phone}"/>
		</mx:FormItem>
		<mx:FormItem label="Address">
			<mx:TextInput id="address" text="{contact.address}"/>
		</mx:FormItem>
		<mx:FormItem label="City">
			<mx:TextInput id="city" text="{contact.city}"/>
		</mx:FormItem>
		<mx:FormItem label="State">
			<mx:TextInput id="state" text="{contact.state}"/>
		</mx:FormItem>
		<mx:FormItem label="Zip">
			<mx:TextInput id="zip" text="{contact.zip}"/>
		</mx:FormItem>
	</mx:Form>

	<controls:PictureInput id="picture" top="14" left="350" styleName="pictureFrame"
		pictureWidth="160" pictureHeight="160"
		source="{contact.pic}" />

	<mx:Label id="status" left="8" bottom="50"/>
	<mx:HBox left="8" bottom="8">
		<mx:Button label="Save" click="save()"/>
		<mx:Button label="Delete" click="remove()"/>
	</mx:HBox>

</mx:Canvas>

Although very simple, the InSync application allows you to open multiple contact views. It’s therefore important for a specific view to be able to know that a specific operation succeeded or failed. For example, in this case, we show a status message when the save operation succeeded.

If you want to work with the Mock Service implementation, you’d define applicationContext.xml as follows:

<?xml version="1.0" encoding="utf-8"?>
<objects>

	<object id="contactService" class="insync.services.ContactMockService"

</objects>

If you want to switch to the RemoteObject implementation, you’d change the applicationContext.xml as follows. No need to change anything in the application, and no need to recompile.

<?xml version="1.0" encoding="utf-8"?>
<objects>

	<object id="remoteObject" class="mx.rpc.remoting.mxml.RemoteObject"	abstract="true">
		<property name="endpoint" value="http://localhost:8400/lcds-samples/messagebroker/amf" />
		<property name="showBusyCursor" value="true" />
	</object>

	<object id="contactRemoteObject" parent="remoteObject">
		<property name="destination" value="contacts" />
	</object>

	<object id="contactService" class="insync.services.ContactRemoteObjectService">
	    <constructor-arg>
	        <object id="myContactRemoteObject" parent="remoteObject">
	            <property name="destination" value="contacts" />
	        </object>
	    </constructor-arg>
	</object>

</objects>

In this version of the application, we achieved an important goal: we decoupled the views of the application from any specific service implementation (and we can easily change the implementation we want to use). Decoupling the service layer from the other layers of your application is Spring AS’ primary objective.

Beyond that, you are free to implement the design patterns that you like, or that best fit the requirements of your application and the makeup of your development team. For example, you may prefer a design pattern where the view doesn’t have a reference to a service or controller (even if that reference is defined as an interface).

One additional benefit of using a Mock Service is that I can host the application configured with the MockService and let you play with it without having to worry about my database.

Click here to run the application. (View Source is enabled)

Installation instructions

NOTE: If you already installed the application provided with part 2 of this series, you can skip steps 1, 3, 4, 5 and 6.

  1. Install the BlazeDS turnkey server. (To be clear: you don’t have to use BlazeDS to use Spring ActionScript… That’s just what this sample is using.)
  2. Download insyncspringas3.zip, and unzip it on your local file system.
  3. Copy insyncspringas3/java/classes/insync to blazeds/tomcat/webapps/samples/WEB-INF/classes/insync.
  4. Add the following destination to blazeds/tomcat/webapps/samples/WEB-INF/flex/remoting-config.xml:
  5. <destination id="contacts">
            <properties>
                <source>insync.dao.ContactDAO</source>
                <scope>application</scope>
            </properties>
    </destination>
    

  6. Copy insyncspringas3/sampledb/insync to blazeds/sampledb/insync
  7. Edit server.properties in blazeds/sampledb, and modify the file as follows to add the insync database to the startup procedure.

    server.database.0=file:flexdemodb/flexdemodb
    server.dbname.0=flexdemodb
    server.database.1=file:insync/insync
    server.dbname.1=insync
    server.port=9002
    server.silent=true
    server.trace=false
    

  8. Start the database (startdb.bat or startdb.sh)
  9. Start BlazeDS
  10. In Flex Builder, create a new Flex project called insyncspringas3. You don’t have to select any “Application server type”.
  11. Copy spring-actionscript.swc and as3reflect.swc from insyncspringas3/flex/lib to the lib directory of your project
  12. Copy the files and folders from insyncspringas3/flex/src to the src directory of your project
  13. Open applicationContext.xml and make sure the remoteObject endpoint value matches your server setup.
  14. Run the application

10 Responses to The Spring ActionScript Framework — Part 3: Injecting Services (and Mock Services)

  1. Mark March 20, 2009 at 2:42 pm #

    I get the error when I run the demo in my pc.it can not open config file.
    why this happen?

    Thanks

    Error Message:

    SecurityError: Error #2148: SWF file file:///C:/FLEXROOT/Spring3/bin-debug/springaspart3.swf cannot access local resource applicationContext.xml?192555. Only local-with-filesystem and trusted local SWF files may access local resources.
    at flash.net::URLStream/load()

  2. Rob Moore March 20, 2009 at 6:40 pm #

    It appears the example is referencing flexlib which isn’t included in the zip. The SWC file can be found at http://code.google.com/p/flexlib/downloads/list.

  3. Brian LeGros March 23, 2009 at 3:56 pm #

    I can see in this example how your ContactMockService will help you test IContactService, but exercising ContactRemoteObjectService could also be benficial from a unit testing perspective. At Fluint, for release 1.2.0, which should be coming out in a month or so, we’re going to be offering stub classes for HttpService and RemoteObject (given time also WebService and URLLoader) that will offer some convenience methods for stubbing out Flex framework classes that talk to the outside world. This can be injected into your service implementation and don’t require external resources (i.e. – db, files, etc). Since we don’t have the notion of type-safe mock frameworks for AS3 yet, and testing async stuff can be tricky, we’re hoping these types of tools will help the community in creating regression testable unit and integration tests.

    If anyone can’t wait, or is interested, I blogged about the initial go at http://www.brianlegros.com/blog/2009/02/21/using-stubs-for-httpservice-and-remoteobject-in-flex/.

  4. Kyle Glossy March 30, 2009 at 1:24 pm #

    Great series Christophe. Thanks for sharing.

    In Part 1, you mentioned that Part 3 would show how to tell the Flex compiler to include the classes that the framework can load dynamically. Based on your code, it looks like you are declaring a couple of private vars that don’t get used by the app, but force the complier to link in the classes.

    // Force the compiler to include these classes
    private var contactService:ContactRemoteObjectService;
    private var contactMockService:ContactMockService;

    It would be great if we could somehow associate these class inclusions with the ApplicationContext object instead of the component that doesn’t really care what implementation it is using. Do you have any thoughts on how that could work?

  5. Mike April 5, 2009 at 5:21 am #

    Kyle Glossy Says: “It would be great if we could somehow associate these class inclusions with the ApplicationContext object instead of the component that doesn’t really care what implementation it is using. Do you have any thoughts on how that could work?”

    This is the disadvantage of using an XML based configuration for Flex IOC. The Flicc framework, http://flicc.sourceforge.net, uses MXML – so you still get the structured configuration layout of XML, but classes are compiled in to the SWF, removing the need for the forced compilation of classes using private unused variables.

  6. JMilo May 27, 2009 at 9:48 pm #

    How to set up the project src and lib files for using the command line flexbuilder?

  7. Jan-Hendrik May 30, 2009 at 6:46 am #

    Hi Christophe!

    Thanks a lot for your high quality articles. They helped a lot in the past to figure out the internals of how things work. I especially liked the LCDS parts!

    Jan-Hendrik

Trackbacks/Pingbacks

  1. 翻译(THE “SPRING ACTIONSCRIPT” FRAMEWORK) « Raven’s Blog - March 24, 2009

    [...] Part 3 注入service [...]

  2. 31/03/2009 « Robertopriz’s Weblog - March 31, 2009

    [...] Christophe Coenraets spring flex  http://coenraets.org/blog/2009/03/the-spring-actionscript-framework-part-3-injecting-services-and-mo… [...]

  3. Christophe Coenraets » Blog Archive » Building a Flex Application with the Parsley Framework - July 16, 2009

    [...] my recent explorations of “Swiz”, and “Spring ActionScript” (1,2,3), I decided to take the new version of the Parsley framework for a test drive, and build the [...]

Leave a Reply

css.php