There's been some talk lately on FlexCoders about Cairngorm's Responder interface and how to use it to handle results from a remote service. Personally, I don't use Cairngorm's Responder. Instead, I use the mx.rpc.IRsponder interface.
The differences are subtle, but I prefer IResponder for the following reasons:
- IResponder ships with the Flex 2 framework
- IResponder integrates well with other classes in the mx.rpc package, specifically AsyncToken
- IResponder allows multiple responders to act on the return value of a remote service call
- Cairngorm's Responder requires some "hard-wiring" and isn't as type-safe (which makes it more error prone)
To understand the last two points, some background on Cairngorm's Responder is needed. Cairngorm's Responder interface has two public methods:
public function onResult(event:* = null):void public function onFault(event:* = null):void
Typically, commands that you create inside of the Cairngorm framework will implement this interface. Commands generally make a remote method call and handle the results of that call all in one place. Every command that makes a remote method call will adhere to the Responder interface and define an onResult and onFault method.
In order to use the Responder interface though, you need a little bit of glue-code behind the scenes. When you define the remote service you have to explicitly add a result and a fault listener in the tag, like this:
// In Services.mxml <mx:RemoteObject id="someService" result="event.token.resultHandler( event )" fault="event.token.faultHandler( event )" ... />
Here you're saying that every result should invoke the resultHandler method and every fault should invoke the faultHandler (passing along the actual event object with it).
The call to the remote service normally happens in a business delegate. An example business delegate class looks something like this:
import com.adobe.cairngorm.business.Responder;
import com.adobe.cairngorm.business.ServiceLocator;
import mx.rpc.AbstractService;
import mx.rpc.AsyncToken;
public class MyDelegate {
private var service:AbstractService;
private var responder:Responder;
public function MyDelegate( callingCommand:Responder )
{
service = ServiceLocator.getInstance().getService( "someService" );
responder = callingCommand;
}
public function makeRemoteCall():void
{
// Pay attention here...
var call:AsyncToken = service.aRemoteMethod();
call.resultHandler = responder.onResult;
call.faultHandler = responder.onFault;
}
}
The delegate gets invoked from the command, passing the command reference ("this") to the delegate's constructor. The delegate saves the command as the responder, and when a remote call is made the delegate wires "resultHandler" to the onResult method of the command and "faultHandler" to the onFault method of the command.
Do you see the problems yet?
What I don't like about this approach is that... well... it just doesn't sit right with me. When you make a remote method call from an AbstractService, you get an AsyncToken returned right back to you. The same token is also returned back from the remote procedure call and can be accessed via event.token in the event handler. This allows you to set token-level data per remote call on a call-by-call basis (you set the data when you make the call, and you can recieve the data when the call comes back). Exploiting this just to implement an Obversable pattern feels like a bastardization of the token's purpose.
See, in order for Cairngorm to handle the response of the call, properties are dynamically added to the token (resultHandler and faultHandler), and then invoked as indirect listeners by the result and fault listeners added to the remote service in Services.mxml.
As you know from ActionScript 1 days, relying on dynamic properties causes all sorts of headaches. I love that ActionScript 3 is still a dynamic language, but for a listener implementation like this it really should be strongly typed. What if you accidentally spell resultHandler wrong when you're adding it to the token, as "resultHandlr"? The compiler offers no help to you there -- you'd have to wait for the Flash Player to throw a run-time exception saying the method "resultHandler" couldn't be found on event.token when the result listener on the remote service itself fires.
Additionally, resultHandler can only ever point to one method, and therefore you're limiting yourself to a single listener. Granted, in the Cairngorm case that's not really a problem since every remote method call is meant to be handled by a single command. But still, the Flex framework allows more flexibility so why not take advantage of it?
So, as I asid before, instead of using Cairngorm's Responder interface I use the mx.rpc.IResponder interface. The key changes code-wise are:
- Remove the result="event.token.resultHandler( event )" and fault="event.token.faultHandler( event )" from the remote service tags in Services.mxml. There is no need to hard-code those listeners anymore.
- Have your Commands implement IResponder instead. This means you need to implement:
public function result( data:Object ):void { // Most of the time the data is a ResultEvent, so just cast it here var event:ResultEvent = data as ResultEvent; } public function fault( info:Object ):void { // Most of the time the info is a FaultEvent, so just cast it here var event:FaultEvent = info as FaultEvent; } - Modify your delegates to support IResponder instead of Responder by changing the public var responder to be of type IResponder, and having the constructor take in an IResponder instead.
- When you make the remote call, instead of assigning resultHandler and faultHandler you just add the responder as a listener:
// Inside of the MyDelegate class public function makeRemoteCall():void { var call:AsyncToken = service.aRemoteMethod(); call.addResponder( responder ); }
In my eyes, using IResponder is a much cleaner implementation. It leverages code already included in the Flex framework, removes some of the glue-code, and makes the final result more flexible in the end.
If the Cairngorm creators agree, perhaps the next release will move away from Responder towards the IResponder approach instead?

2 Comments
Hi Darron,
You've added to a lot of the internal discussion we're having with the Cairngorm team about how we handle responders. I've seen the IResponder interface used elsewhere too in the same manner and it does fit well.
We'll be having more discussions about it, but I wouldn't be surprised if this finds its way into a future release.
Posted by: Alistair McLeod | July 6, 2006 11:47 AM
Nice, Darron. I wouldn't be surprised if this finds its way into Arp as well :)
Posted by: Aral Balkan | July 6, 2006 1:09 PM