Go Tripod

Building a medium to large scale Flex App using Cairngorm (Part 4)

In this final part on building a medium to large scale flex or Air app using Cairngorm, I will look at how I approach the service locator and use flex builders built in responder mechanism to handle service call responses.

Services

In part 1 we created a ServiceLocator singleton using the same method as we did for our ModelLocator. We will use this in a minute when we register our Service with the ServiceLocator.

I like to create a proxy class for my services.

package uk.co.clockobj.cairngormdemo.controller.services
{
	import mx.rpc.AsyncToken;
	import mx.rpc.IResponder;
	import mx.rpc.remoting.RemoteObject;

	public class UserService
	{
		private var ro : RemoteObject;

		public function UserService()
		{
			ro = new RemoteObject();
			ro.destination = "userService";
		}

		public function logInUser( username : String, password : String, responder : IResponder) : void
		{
			var at : AsyncToken = ro.logInUser( username, password );
			at.addResponder( responder );
		}

	}
}

In this example we want to call a destination method called logInUser() passing a username and password as parameters. Here I am using the RemoteObject class to work with a backend server such as LiveCycle, BlazeDS, Flamingo, WebOrb etc, via AMF (Actionscript Message Format), but you can use this with HTTPService, WebService, etc.

Notice that along with the two parameters that our service call will need, I have added a responder of type IResponder. I am going to use the method championed by Darron Schall where we use Flex’s built in responder and AsyncToken mechanism.

We need to now register this service on our ServiceLocator.

package uk.co.clockobj.cairngormdemo.controller
{
	import uk.co.clockobj.cairngormdemo.controller.services.ProductService;
	import uk.co.clockobj.cairngormdemo.controller.services.UserService;

	public class ServiceLocator
	{
		private static var _instance:ServiceLocator;

		public static function getInstance():ServiceLocator {
			if (_instance == null) {
				_instance = new ServiceLocator();
			}
			return _instance;
		}

		public function ServiceLocator()
		{
			if ( _instance != null )
			{
				throw( new Error( "Singleton class has already been instantiated" ) );
			} else
			{
				// Instantiate services here
				userService = new UserService();
				productService = new ProductService();
			}
		}

		public var userService : UserService;
		public var productService : ProductService;

	}
}

IResponder

The IResponder interface that we used in our service enforces any class that implements it to provide a result( data : Object ) and fault( info : Object ) method. Due to the Asynchronous nature of Actionscript 3, the AsyncToken ensures the responder we provide handles that particular service call. Regular Event handlers to not offer this.

We are going to get all the commands we create (that make service calls) to implement this IResponder interface, in addition to the ICommand interface. We can then pass the command to the service as the responder for the service call:

So for a command that gets the ProductDTOs we used in Part 2, using for example a getProducts() method call on a productService destination.

Service:

package uk.co.clockobj.cairngormdemo.controller.services
{
	import mx.rpc.AsyncToken;
	import mx.rpc.IResponder;
	import mx.rpc.remoting.RemoteObject;

	public class ProductService
	{
		private var ro : RemoteObject;





		public function ProductService()
		{
			ro = new RemoteObject();
			ro.destination = "productService";
		}

		public function getProducts( responder : IResponder ) : void
		{
			var at : AsyncToken = ro.getProducts();
			at.addResponder( responder );
		}

	}
}

(We register this with our ServiceLocator as we did for the UserService)

GetAllProductsCommand:

package uk.co.clockobj.cairngormdemo.controller.commands
{
	import com.adobe.cairngorm.commands.ICommand;
	import com.adobe.cairngorm.control.CairngormEvent;

	import mx.rpc.IResponder;
	import mx.rpc.events.ResultEvent;

	import uk.co.clockobj.cairngormdemo.controller.ServiceLocator;
	import uk.co.clockobj.cairngormdemo.model.ModelLocator;

	public class GetAllProductsCommand implements ICommand, IResponder
	{
		public function GetAllProductsCommand()
		{
		}

		public function execute(event:CairngormEvent):void
		{
			ServiceLocator.getInstance().productService.getProducts( this );
		}

		public function result(data:Object):void
		{
			ModelLocator.getInstance().product.dtos = ResultEvent( data ).result;
		}

		public function fault(info:Object):void
		{
			// Fault handler code
		}

	}
}

Once this command is registered with our controller we can dispatch the relevant event form anywhere in our application to load all the products into our model.

Notice above how the command implements the IResponder interface and passes itself to the service to act as the responder for the service call. We then get to handle the service response and fault from within the command.

Chaining Commands Together (Revisited)

With this knowledge, we can return to our SequenceCommand example where we want to log a user in and then call the ChangeApplicationStateCommand to change the view state of Main.mxml to the logged in user state or logged in Admin state

package uk.co.clockobj.cairngormdemo.controller.commands
{
	import com.adobe.cairngorm.commands.SequenceCommand;
	import com.adobe.cairngorm.control.CairngormEvent;

	import mx.rpc.IResponder;
	import mx.rpc.events.ResultEvent;

	import uk.co.clockobj.cairngormdemo.controller.AppController;
	import uk.co.clockobj.cairngormdemo.controller.ServiceLocator;
	import uk.co.clockobj.cairngormdemo.controller.events.LoginEvent;
	import uk.co.clockobj.cairngormdemo.model.ModelLocator;
	import uk.co.clockobj.cairngormdemo.model.dto.UserDTO;
	import uk.co.clockobj.cairngormdemo.model.session.SessionModel;

	public class LogUserInCommand extends SequenceCommand implements IResponder
	{
		public function LogUserInCommand()
		{
			this.nextEvent = new CairngormEvent(AppController.CHANGE_APP_STATE);
		}

		override public function execute(event:CairngormEvent):void
		{
			var le : LoginEvent = LoginEvent( event );
			ServiceLocator.getInstance().userService.logInUser( le.username, le.password, this );
		}

		public function result(data:Object):void
		{
			ModelLocator.getInstance().session.userDTO = UserDTO( ResultEvent( data ).result );

			if ( ModelLocator.getInstance().session.loggedInUser.isAdmin )
			{
				this.nextEvent.data = SessionModel.APP_STATE_ADMIN;
			}
			else
			{
				this.nextEvent.data = SessionModel.APP_STATE_LOGGED_IN;
			}
			executeNextCommand();
		}

		public function fault(info:Object):void
		{
			// Fault handler code goes here
		}

	}
}

To use Cairngorm’s SequenceCommand feature, our command has to extend SequenceCommand. SequenceCommand already implements ICommand but this means we need to override the execute() method and we still need to implement IResponder as we want this class to handle the response from the userService.

In the commands constructor we set the SequenceCommands nextEvent property which takes the CairngormEvent you want to dispatch from this command. When you are ready you can dispatch the event using the SequenceCommand’s executeNextCommand() method.

Our ChangeApplicationStateCommand looks for the state we want to change to in the data property of the CairngormEvent so we set the relevant one in the result handler and dispatch.

As I previously demonstrated for the Product model, I have implemented similar code to convert the UserDTO returned from the service to a User model item object. The User object has some business logic in it to identify whether the user is an Admin (which is not in the DTO).

As you can see although Cairngorm adds a fair bit of code, in the long run we have a more organised and consistently centralised configuration which makes it easier to maintain, test etc. As I stated in Part 1 my method is not the documented way of using Cairngorm but has been adapted over use on several large projects to the solution that works best for us. I hope this series is useful to anyone starting out with Cairngorm and I do recommend you look at EasyMVC and alternatives to Cairngorm such as PureMVC (I have never used this but only because Cairngorm meets my needs).

I have also attached the source files I used to write this article which i hope will help you see how everything hangs together.: Cairngorm Demo Source Files

Contents:

Part 1 – Setting up a project for Cairngorm
Part 2 – The Model
Part 3 – Commands
Part 4 – Services
Cairngorm Demo Source Files

Sharing is caring:

Got an idea for a project?

Need a website? Web-enabled software to streamline your business? Just some advice?