May 9 2007

Model-Glue:Flex Service Autoproxies

Posted by Joe Rinehart at 12:40 AM
13 comments
- Categories: Flex | Model-Glue:Flex | Cairngorm

I've floated a few .zips of Model-Glue:Flex around, and so far, so good. However, one person who's not currently doing much Flex development had what I feel will be a key piece of feedback: the early zip did a lot to implement implicit invocation and the friendliness of Model-Glue, but it did nothing to remove the boilerplate that often goes along with writing a Cairngorm application that uses a service layer.

The Problem

A lot of Flex developers, myself included, use Business Delegates to separate the implementation of server-side services from the rest of our application.

Big term, easy concept: the delegate just wraps all the functions of the service.

Then, when we want to call the server, a Command calls the method on the delegate, stating that the Command should be used as the responder.

This means that any time I'd like to work with a new server-side operation, I've got a few things that are going to either slow me down or mess me up:

  1. I've got to add boilerplate to the delegate:

    public function doSomething():void {

    var token:AsyncToken = service.listDynamicStrings();

    token.addResponder(command);

    }

  2. I've got to implement responder functions:

    public function result(data:Object):void { }

    public function fault(data:Object):void { }

  3. If, for some reason (rare, but I've hit it), I need to make two server-side requests in one command, I've got to monkey about with responder delegates to get them to call different methods in the command.

While it's not *that* much code to write, when you stack it on top of creating the Command class (which is really not much but a procedural script), an Event class, and having to relate the two in the Controller, there's a lot of typing to be done.

So, my earlier reviewer basically said "Can you get rid of the yuck? Maybe you could write a dynamic proxy, handling methods not being found, that'd wrap a service and let you assign result/fault functions?"

Model-Glue:Flex's Solution

Ugly terms like AsynchronousMethodProxy aside, I've cooked up something that has a real feel of Model-Glue "easiness" to it - it's a little loosely typed, but darned easy to use.

The "autoproxy" lets you ask for any AbstractService (like a RemoteObject tag) in your ServiceLocator and make arbitrary method calls, using the name of the server-side function (e.g., <cffunction>). You just pass it whatever arguments the function requires, an optional result function, and then an optional fault functions. It's a lot like prototype's style of making Ajax calls.

Ok, code works better than words. This is all you have to do to ask for a list of contacts and use it as the source of an ArrayCollection in a model locator:

// In controller
private var service:AbstractService = getService("contactService");
private var model:ModelLocator = ModelLocator.getInstance();

// Listener function in controller
public function listContacts(e:ModelGlueEvent):void {

   service.list (
      function(data:Object):void {
         model.contacts.source = data.result as Array;
      }
   )

}

Let's spice it up and add a filter string as an argument and a reference to a common fault function:

// Listener function in controller

public function listContacts(event:ModelGlueEvent):void {

   service.list (
      event.getValue("searchString"),
      function(data:Object):void {
         model.contacts.source = data.result as Array;
      },
      this.serverError
   )

}

private function serverError(data:Object) {
   Alert.show("Oops!");
}

Any takers, thoughts, flames?

Comments

Zach Stepek

Zach Stepek wrote on 05/09/07 2:58 AM

It looks nice and flexible (no pun intended) so far! I'm looking forward to getting my hands on this and applying in to future projects.
Sean Corfield

Sean Corfield wrote on 05/09/07 9:51 PM

I really like where this is going - the difference between the original Cairngorm app and your first &quot;Model-Glue-ification&quot; was quite dramatic and the second variant makes a huge difference! Now that I can see I don't have to write all that repetitive code, I could grow to like Flex!
Christophe Herreman

Christophe Herreman wrote on 05/10/07 2:00 AM

Hi Joe,

I have also been questioning the repetitive work for events/commands/business delegates in Cairngorm and ARP apps and wanted to come up with a solution myself, but this seems to be going in the right direction (so I'll stop my efforts ;-)).

How are you handling the controller? It is still a FrontController that handles all the requests or do you have a controller per entity?

I'm very interested in this! Please if you have the time, hook me up with a pre-release so I can play around with it ;-) Also if you are interested, I'm working on an IoC container for AS3 (http://sourceforge.net/projects/prana/). This might be a powerful combination.
Tom Chiverton

Tom Chiverton wrote on 05/10/07 4:37 AM

It looks like it keeps all the good bits of Cairngorm, but reduces the amount of repetative tasks.
We might consider using it in a small project as an alternative to see how it goes, once it's released.
Brian Rinaldi

Brian Rinaldi wrote on 05/10/07 9:08 AM

This sounds really interesting. We use Cairngorm here at work and it is a lot of work (though I feel that once you are in the midst of the workflow it moves along at a decent clip). Anyway, on a side note, your Model-Glue:Flex blog category doesn't seem to work.
darron

darron wrote on 05/10/07 9:13 AM

I think you're over-simplifying step #1; it's not always boilerplate code.

The point of the delegate is to handle the interaction with the server. It does more than just wrap method calls, it also includes serialization and deserialization of instances into a data exchange format that the delegate/server agree upon.

In the case of connecting to a ColdFusion server using RemoteObject, then you're correct that step #1 is boilerplate. The reason for this is because, when using RemoteObject, the Flash Player and ColdFusion both have AMF translators, so you don't have to write any code for a data exchange format. It's built in, and happens automatically behind the scenes.

However, in the case of connecting to a plain old web service (or using xml-rpc, or even JSON), where you have to massage the data on your own, then often step #1 includes some custom code that translates the server's plain-text response into native instances. For example, you might pull down an XML packet and have to convert that into an ArrayCollection of Book value objects. Then, after conversion, those instances are passed back to the command's result handler.

This results is some additional handlers, and you don't blindly just set the asyncToken's responder to the invoking command. The delegate intercepts the server's response, does some magic first, and THEN passes that along.

Since you're a ColdFusion guy, you've probably never had to deal with the above, and it probably has no impact on MG:F since people using ModelGlue are using ColdFusion. Therefore MG:F implcitly requires communication with a ColdFusion server, and that's probably assumed anyway.

But, I wanted to point it out because there's more out there to interop with besides just ColdFusion. Just some food for thought.
Christophe Herreman

Christophe Herreman wrote on 05/10/07 9:45 AM

Hi Darron, I see your point. We currently also call different services (web services, http-requests and remote services) but don't handle the massaging in the business delegates. Instead we pass the received data to a deserializer which massages the data and returns the actual objects. It seems cleaner because the deserialization is seperated from the remote call and we can write a separate unit test for the deserialization.

So in case of MG:F, we could do the same thing:
// In controller
private var service:AbstractService = getService(&quot;contactService&quot;);
private var model:ModelLocator = ModelLocator.getInstance();
private var contactsDeserializer:IContactsDeserializer = new XMLContactsDeserializer();

// Listener function in controller
public function listContacts(e:ModelGlueEvent):void {

service.list (
function(data:Object):void {
model.contacts.source = contactsDeserializer.execute(data.result);
}
)

}

Thoughts?
darron

darron wrote on 05/10/07 10:26 AM

Sure, I also use testable utility classes for object translation. I wasn't suggesting that you put the translation code directly in the delegate, merely pointing out that the delegate's responsibility is to make sure that the translation gets done.

I don't like the code you suggested because its moves the responsibility of the translation out of the service layer and into the result-processing layer. If you change the service layer to something where translation isn't required, or a new deserializer needs to be used, you have to find all of the places in your code where contactsDeserializer.execute is referenced. Suddenly, the result-processing layer is coupled to the data-exchange format.

However, if you keep this as part of the result-handling-middle-man in the delegate, you get a more cleanly separated business/transport layer, and the result-processing layer has no dependency on knowing the format the server actually responds with. It just deals with the results that it expects as native instances.

For example, I prefer to keep my command/result processing layer data-format-agnostic. I assume that all data coming back from the server is in a native format. It's the delegate's job to put the data in that format (through the use of utility translation classes, of course). When I call listBooks(), I expect the response to be a strongly-typed ArrayCollection of Book, even if the server returns me just a generic JSON string. The comamnd's result handler deals with the ArrayCollection because the delegate deals with the translation of the server's response. The resultEvent.result property is my ArrayCollection, and no translation is done by the command.
Josh

Josh wrote on 05/10/07 5:16 PM

This conversation helped me understand the purpose of delegates. I've been skipping them all-together (and communicating with the services directly from my commands) because they seemed like a useless step with nothing but a server call. Thanks, guys.
Joe Rinehart

Joe Rinehart wrote on 05/11/07 9:55 AM

Hi Darron,

&gt;I prefer to keep my command/result processing layer data-format-agnostic.

When necessary, me too. I wouldn't advocate the autoproxy approach for all situations: just ones where it's appropriate.

My first rough cut of MG:F that I sent out to a few people included a more traditional delegate approach that abstracted all knowledge of backend services out of the controller layer, just as a normal Cairngorm app would do (along with all the boilerplate that it requires!).

The point of the autoproxies isn't to lock people in to a given implementation: it's to let them use an easier implementation when it's appropriate instead of going full-bore delegate when it's just not necessary. The fact that the Cairngorm community is making heavy use of code generation to generate controller code is a warning sign for me - it'd be hard to convince someone used to the lightweight world of Ajax development to this approach!

One thing I'd _really_ like to look into (now that I know how to proxy and I hear an IoC container for AS3 is in the works) is an AOP implementation in AS3 that's combine the two worlds: you use the autoproxy bits, but wrap data massaging advice on the proxy. Chris, you reading this? :)
Simeon

Simeon wrote on 05/12/07 12:05 PM

I like the idea of shortening the code required to make the remote calls happen. However, i have to side with darron on this one. Although I am not a fan of all the layers of code I have to write to set up an application. But with Cairngorm I feel like 75% of that work is up front and its not so bad with ongoging development.

Beyond that point though it is very common for me to build the entire flex interface before even considering how the back end will be implemented. I use delegates to let my application know what should be being returned. This allows me to build a fully functional prototype for my clients which in the end only the delegates have to be changed to connect to the back end server. This allows me to develop my application with out many headaches of actually having the front end tied into the backend.

So while I like the simplification of the process, I think it also takes away much of the power and flexibility that comes from using the different layers of Cairngorm. I think one of the biggest problems with Cairngorm is that it is seen as an all or nothing framework. But unlike the CF frameworks you can use pieces of CG without implementing the whole thing. And then you only integrate the &quot;boiler plate&quot; pieces that actually add some benefit to your application.

Just my thoughts. Simeon - Out.
mike nimer

mike nimer wrote on 05/14/07 4:51 PM

I have to agree with Darron on this too. I don't actually like about 1/2 of Cairgorm but the other 1/2 I do like. Especially its use of the Service Delegate pattern. However, there is a reason it?s a defined pattern (the Cairgorm developers didn?t make it up). But with any ?pattern? remember it is there to help you, if you have the specific problem it was designed to solve, not a requirement to use it because it exists.

For me, the service delegate layer provides a clear place for me to replace real data with Stub data. If my application is going through a service delegate layer, I can easily point the app to stub data, local cached data, or real data. And the rest of the app could care less.

And an argument for putting your serialization and deserialization in the delegate. Look at the Flickr API, it has the same methods exposed with different protocols; JSON, REST, XMLRPC, etc. API. If I build my app against those methods, and leave the logic to talking to the different protocols in the delegate. I can easily switch the app between protocols by switching the delegate without modifying code throughout the application.
watch film online

watch film online wrote on 04/14/08 7:17 AM

http://wisbeck.info Thousands movies in one place.

Write your comment



(it will not be displayed)