Model-Glue:Flex Service Autoproxies

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?

Model-Glue:Flex?

I figured I'd better blog this while I had some downtime at the cf.objective conference. In my Model-Glue session today, I previewed an experimental AS3 Model-Glue implementation of Model-Glue, providing a lighter weight / looser / more implicit architectural framework in comparison to something like Cairngorm.

There's no docs, I've written one sample app, and I'm not sure this'll ever be an 'official' Model-Glue thing, but I'd like to ask if anyone would be interested in taking a look: leave a comment and I'll send out a zip when it's ready...I need to do an FDS example and a Quickstart.

Adding a "ViewController" into your Cairngorm app

The other day, I posted about adding Cairngorm event listeners directly into your views. Just summarizing it like that, it should already sound like a wonky alternative to using ViewHelper/ViewLocator (which, to me, is even wonkier).

Here's my situation:

I have a login form that I'd like to have to something "special" when login fails, like blink or turn red or play a very loud siren noise. Whatever that is, I can't get there by just binding to a "workflow" value in the ModelLocator: I need to call a function in the view.

Jay Proulx posted a possible solution that created a separate "ViewController" concept, where a class would extend Canvas and define string constants representing various states. It's a nice way to separate the listeners out of the views, but it still doesn't go where I need to be: having functions that play things like effect sequences in response to architectural events.

So, I combined his idea, mine, and the basic UI event system to create my own ViewController class.

Here it is (nothing special):

package com.foo.control

{
   import mx.core.UIComponent
   import com.adobe.cairngorm.control.CairngormEventDispatcher;

   public class ViewController extends UIComponent
   {
    private var dispatcher:CairngormEventDispatcher = CairngormEventDispatcher.getInstance();
   
    protected function addListener(eventName:String, func:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void {
       dispatcher.addEventListener(eventName, func, useCapture, priority, useWeakReference);
    }
   }
}

Nothing fancy. Extends UIComponent and adds an addListener() method that'll add a listener to the CairngormEventDispatcher.

However, I can then extend it with something like LoginViewController. LoginViewController adds a listener for the USER_LOGIN_RESULT_EVENT, and depending on the authentication status of the user involved, raises a plain old UI event:

package com.foo.view.controllers
{
   import com.foo.control.ViewController;
   import com.foo.control.user.AuthenticationController;
   import com.foo.control.user.UserLoginResultEvent;
   import com.foo.model.user.User;
   import flash.events.Event;

   [Event(name="loginFailed",type="flash.events.Event")]
   [Event(name="loginSuccessful",type="flash.events.Event")]
   
   public class LoginViewController extends ViewController
   {
      public function LoginViewController() {
         addListener(AuthenticationController.USER_LOGIN_RESULT_EVENT, loginResult);
      }
      
      private function loginResult(result:UserLoginResultEvent):void {
         var u:User = result.user;
         var e:Event;
         
         e = u.authenticated ? new Event("loginSuccessful") : new Event("loginFailed");
         dispatchEvent(e);
      }
   }
}

Now I'm in business. Instead of dealing with views by an arbitrary ID (my main issue with ViewLocator/ViewHelper) or giving my view knowledge of the Controller view (my main issue with my earlier workaround), any view that wants to deal with login failures or login succeses can do this:

<viewControllers:LoginViewController loginSuccess="doSomething()" loginFailure="playAnnoyingSirenSound()" />

It's a bit like ViewHelper, but backwards enough where it works implicitly instead of explicitly.

Stupid Cairngorm Trick: "View Listeners"

I like Cairngorm. I know some folks see it and say "whoa...that's a lot of classes for a single gesture. Events, Commands, Controllers, Delegates, Locators...aieee." However, "everything in its place" mentality and separation of concerns it enforces really appeal to me.

However, one thing I absolutely can't stand is the ViewLocator/ViewHelper bit. The design forces causing it (separation of controller from knowledge of currently existing views and their implementation) are natural, and require a solution. I'm just not sure ViewHelper/ViewLocator are the right answers. They leave me writing code that still knows about the ID strings with which ViewHelpers have registered themselves.

Let's wrap a use case around this: I have a UserLoginEvent mapped to UserLoginCommand. When the UserLoginCommand gets its response from the backend server, I've got some view behavior that I'd like to take place that goes beyond what binding to the ModelLocator can provide. In other words, I need to call a function named loginFailed() on the view. Using ViewHelper/ViewLocator, I can add a ViewHelper to the view that exposes a loginFailed() function that the command will call: its purpose is to call the actual loginFailed() function on the view, disconnected the view's implementation from the command/controller layer. Complicated.

I started looking through the core Cairngorm code last night, and I noticed that the CairngormEventDispatcher's addEventListener() message just wants two parameters: the name of the event listened for and a reference to a listener function.

In Model-Glue terms:

<message-listener name="someMessage" function="someFunction" />

In Cairngorm, as in Model-Glue, the function referenced is normally on a controller.

However, unlike Model-Glue (and HTML apps in general), Flex/Flash gives us the ability to have views respond and change their display without reloading the page. This means there's no technical reason why the listener function couldn't be on a view.

By adding functions acting as intialized() and removed() handlers, you can add code to your view that listens for events, just like the controller:

private function addViewListeners():void {
   CairngormEventDispatcher.getInstance().addEventListener(AuthenticationController.USER_LOGIN_RESULT, loginResults);
}

private function removeViewListeners():void {
   CairngormEventDispatcher.getInstance().removeEventListener(AuthenticationController.USER_LOGIN_RESULT, loginResults);
}

Now, through a sequence of events, I can have the UserLoginEvent's handling code fire a UserLoginResult event. When it's dispatched, my view will "hear" it and fire its loginResults function. Simple.

Conclusion

The CairngormEventDispatcher allows for functions that aren't just methods on the controller to be added as event listeners. By taking advantage of this, we can allow views to respond to architectural events as well as controllers.

It's not without problems: our view is suddenly bound to Cairngorm's event architecture. I'm also not sure how much this stretches MVC...the model's still completely separate, which is my largest concern. It's just that the views are taking on some knowledge of the controller tier (names of events, existence of certain controllers used to get those names, the dispatcher itself) that I'm not sure they should have.

Thoughts?

© 2008 Firemoss, LLC
BlogCFC was created by Raymond Camden. This blog is running version 5.8.001.