Agile software development using Kanban & Scrum. We code Flex & Ruby on Rails in Auckland, New Zealand.

  • Static Controller in Robotlegs

    I’ve been away for a while in the land of the Ruby Object model and Rails. Upon my return to some Flex development, I decided that, although Robotlegs MVC ‘Controllers’ are really nice, they require too much boilerplate. One thing I found in RoR, is that although they use static controller classes with many command methods, most folk follow convention and avoid introducing wads of business logic and tightly coupling their code.

    The good things about RL-MVC Commands

    RL-MVC Commands enforce loose coupling. It’s really hard to end up with complex, tightly coupled controllers when every ‘action’ is a short lived class.

    Commands are dynamic. You can change the existing command set on the fly. I’m not sure where this is useful, I don’t take advantage of it, but that’s not to say that other people do not.

    The tradeoff

    With awesome power comes diminished responsibility. Yeah, that decoupling that Commands give you, removes the need for you to pay as much attention to OO and MVC best practices. Instead, you pay the price by having quite a lot of boilerplate:

    One command class per ‘action’ (read: method)

    I never did mind this tradeoff. Now It’s wearing thin. Our app is big, and there is a lot of boilerplate. After spending some time learning to write Ruby DSL’s it’s really grating to see that a simple and sensible idea reads so badly in code. If I was in Ruby I would have something like:

    # controller_map.rb
    
    map RubbishReminderEvent, PutTheRubbishOutCommand
    
    # put_the_rubbish_out_command.rb
    class PutTheRubbishOutCommand
      def initialize 
        house.nature_strip << house.rubbish_bin
      end
    end
    

    Minimal boilerplate, just intention. Actually, with a decent DSL, you could do away with all of the boilerplate.

    However if you look at Rails, events (http requests) are routed by “concept”. In the de-facto Rails convention a “concept” is a resource, but it does not need to be. The ‘routes’ tell Rails how to map incoming events to controllers.

    In Rails, the controllers are classes, by “concept”, with action methods. This runs the risk of being abused, and it sometimes is. However, people who care do not make a mess of their controllers. So I decided to assume my developers are good people, and make a compromise to remove some boilerplate.

    The boilerplate

    As a reminder, this is the boilerplate I’m talking about

        import ....  // boilerplate
    public class PutTheRubbishOutCommand extends Command { // Boilerplate
    
                // Boilerplate
        [Inject] 
        public var event:TimerEvent;
    
                // Often repeated, not 'DRY' 
        [Inject]
        public var rubbishModel:RubbishModel;
    
                // The only bit of actual 'intent'
        override public function execute():void {
            rubbishModel.putRubbishBinOnNatureStrip;
        }
    
    }
    

    Static Controller

    I propose using a Static Controller. Here, “static” is an antonym of “dynamic”, not the keyword

    This is more like a Rails Controller which groups ‘action methods’ and reduces boilerplate:

    public class RubbishController extends Controller {
    
        [Inject]
        public var rubbishModel:RubbishModel;
    
        public function RubbishController() {
            addMethod(TimerEvent.RUBBISH_NIGHT, putTheRubbishOut);
            addMethod(HomeOwnerEvent.GO_TO_WORK, bringTheRubbishIn);
        }
    
        private function putTheRubbishOut(event:TimerEvent):void {
            rubbishModel.putRubbishBinOnNatureStrip()
        }
    
        private function bringTheRubbishIn(event:HomeOwnerEvent):void {
            if (!rubbishModel.rubishBinIsOnNatureStrip())
                return;
    
            rubbishModel.putRubbishBinRoundBack();
        }
    
    
    }
    

    I think this looks good. At this point I should apologise to Jesse Warden for admonishing him when he suggested to a conference room at FATC that it was o.k. to do this.

    I still think it’s not “o.k.” because if you don’t grasp the value of seperation of concerns, you will make a mockery of this approach to controllers. If you’re starting out with Robotlegs, do not use this approach!

    Addendum

    Extra reading: How I aim to implement the static controller

    The heart of a Static controller is Controller which looks a bit like this:

    public class Controller extends Actor {
    
        public function dispose():void {
            eventMap.unmapListeners();
        }
    
        protected function addMethod(eventType:String, callback:Function, eventClass:Class = null):void {
            eventMap.mapListener(eventDispatcher, eventType, callback, eventClass);
        }
    }
    

    All it does is give you the ‘addMethod’ event and allows ControllerMap to tear it down via ‘dispose’ as the context dies.

    addMethod is a similar to CommandMap#mapCommand, except it doesn’t map a class, it maps a method.

    Also, I need to extend Context to provide/manage ControllerMap.

    ControllerMap collects and disposes of Controllers. It won’t do much more than:

    public function mapController(controllerClass:Class):void {
        injector.mapSingleton(controllerClass);
        controller:IController = injector.getInstance(controllerClass);
        controllers[controllerClass] = controller; // for teardown later
        }
    

    I realise that some people may consider the ‘routing’ and the controller to be different responsibilities. As it stands, I cannot figure out a way of keeping routing seperate, because the Controller#actionMethods have to have a ‘thisObject’ of Controller. It would mean a really dopey looking router method like this:

    addMethod(TimerEvent.RUBBISH_NIGHT, RubbishController, RubbishController.putTheRubbishOut);
    

    Which would get worse when you need to enforce the event type:

    addMethod(TimerEvent.RUBBISH_NIGHT, RubbishController, RubbishController.putTheRubbishOut, TimerEvent);
    

    yick

    1. vworkdev posted this