Modular Robotlegs

WTF is a Modular?

Modular programming is a versatile technique for separating an application into smaller parts. Each module is effectively an application and can be developed independently from one another. In a typical modular application you will have a Shell that is loaded initially. The Shell will manage the loading of modules and displaying their contents. Flex modules can be visual components that extend the Module class, but this is certainly not the extent of what can be considered a module.

Robotlegs is well suited for modular application development. Each Context is encapsulated and provides its own internal method for communication. A barrier to writing modular applications thus far with Robotlegs is that while these Context objects can exist in abundance in an application, they aren’t very useful from a modular standpoint unless they can communicate with one another. As with many things within the Robotlegs eco-system, this is best accomplished through the development of a utility that can be used in conjunction with the core framework to provide the specific functionality that we are looking for. There has been some good work done in this area, but with Robotlegs 1.1 and more specifically the 1.5 release of the default Dependency Injection provider SwiftSuspenders, we have been equipped with better tools to accomplish modular contexts in a Robotlegs application in a clean effective manner.

A Little Backstory

Prior to Robotlegs I used PureMVC exclusively. There is a utility for PureMVC called Pipes, which I have written about previously. Pipes is pretty cool. It uses a plumbing metaphor to describe the connections between modules. It is… verbose… and requires a shit-ton of wiring code to be functional. This can be a challenge to get your head around, and can create code that you have to stare at for a good while to fully understand HTF everything is actually being wired.

My original path for a Robotlegs modular utility was to port Pipes. In fact, I didn’t really have to port Pipes, but just wrote an adapter that allowed Pipes to fit into a Robotlegs application. The problem was that it didn’t feel like Robotlegs. Verbose and confusing goes against the core moral fiber of what Robotlegs is all about. So I started whittling it down. At that point it looked like Stray’s excellent work with the Robotlegs Modular utility. Heh. Full Circle! So instead of using Pipes, I’ve taken her work and expanded on it for Robotlegs 1.1 and at the same time clarifying some of the concepts within the utility and making it more useful across a broader range of use cases.

Where we are now

So with Pipes set aside for now, we have a dead simple modular implementation that can be used for Flex, AS3, and maybe even Flash applications. It doesn’t provide all of the functionality that Pipes brings to the table. Specifically it is missing concepts like message filtering and queuing. While these are likely useful tools, I didn’t feel that they needed to be implemented just yet. I have some ideas about how they might be implemented, but feel that the Robotlegs Modular Utility is clean and simple covering a big majority of typical use cases. Start simple and expand from there. This is where I am relying on you, gentle reader, to help guide the utility into something more useful while still keeping the clean and simple Robotlegs aesthetic.

With that behind us, lets look at an example of a modular application written with the Robotlegs Modular Utility. It is fairly useless, but covers the core concepts of what a modular application should be able to do.

Modular Doodads: A Lame but Functional Example

Get Adobe Flash player

The full source for this example can be found HERE…

It was noted in the comments that this gets sluggish after more than a few Doodads are added. This is a Flex invalidation issue. Here is the example in pure AS3 with MinimalComps (view source is enabled)

In this example you have three separate modules: the Shell, or the main application, a logging module that provides “console” output, and Doodads. Doodads are simple modules with not a whole lot of functionality. When you add a Doodad the shell creates a new Doodad and adds it to a container. The Doodads have a “request” button that will ask any other Doodads to change color. In addition to that, they have a close button to remove the Doodad. Above the Doodad container is a “trigger” button. This sends out an event that triggers a command on all of the Doodad modules (causing them to flash violently).

ModularDoodadsContext.as

1
2
3
4
5
6
7
8
9
10
11
public class ModularDoodadsContext extends ModuleContext
{
    override public function startup():void
    {
        //map the modules so that instances will be properly supplied (injected) with an injector.
        viewMap.mapType(LoggerModule);
        viewMap.mapType(DoodadModule);
 
        mediatorMap.mapView(ModularDoodads, ModuleDoodadsMediator);
    }
}

The main shell application context context is doing a couple of things. As with the other modules within the application, this Context extends ModuleContext. ModuleContext will create the ModuleEventDispatcher (IModuleEventDispatcher) as well as a ModuleCommandMap that can be used to map commands that respond to events on the ModuleEventDispatcher. ModuleContext is a convenience mechanism.

Since the modules in this application are view components we can use the ViewMap to map their types. This facilitates injection into the modules when they are added to the stage. The reason for this will become clear as we look at one of these modules.

LoggerModule.mxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?xml version="1.0" encoding="utf-8"?>
<mx:Module xmlns:fx="http://ns.adobe.com/mxml/2009"
           xmlns:s="library://ns.adobe.com/flex/spark"
           xmlns:mx="library://ns.adobe.com/flex/mx"
           implements="org.robotlegs.utilities.modular.core.IModule"
           layout="absolute" width="100%" height="75">
    <fx:Script>
        <![CDATA[
            import org.robotlegs.core.IContext;
            import org.robotlegs.core.IInjector;
            import org.robotlegs.utilities.modular.core.IModule;
            import org.robotlegs.utilities.modular.core.IModuleContext;
 
            import robotlegs.examples.modulardoodads.modules.logger.skins.LoggingTextArea;
 
            protected var context:IModuleContext;
 
            [Embed(mimeType='application/x-font', source="assets/AnonPro.ttf", fontName="Anon")]
            private var anon:Class;
 
            [Bindable]
            public var messages:String = "";
 
            public function addLoggingMessage(message:String):void
            {
                message += "\r";
                messages += message;
                scrollToMax();
            }
 
            private function scrollToMax():void
            {
                messageDisplay.validateNow();
                messageDisplay.scroller.verticalScrollBar.value = messageDisplay.scroller.verticalScrollBar.maximum;
            }
 
            /**
             * We need to initialize our context by setting the parent
             * injector for the module. This is actually injected by the
             * shell, so no need to worry about it!
            */
            [Inject]
            public function set parentInjector(value:IInjector):void
            {
                context = new LoggerModuleContext(this, value);
            }
 
            public function dispose():void
            {
                context.dispose();
                context = null;
            }
 
        ]]>
    </fx:Script>
    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>
    <s:TextArea
        id="messageDisplay"
        fontFamily="Anon"
        fontSize="12"
        width="100%" height="100%"
        text="{messages}"
        skinClass="robotlegs.examples.modulardoodads.modules.logger.skins.LoggingTextArea"/>
</mx:Module>

Looking at the LoggerModule.mxml it is important to note that it implements the org.robotlegs.utilities.modular.core.IModule interface. This provides a contract to ensure that we supply the appropriate API to initialize the module. The IModule interface provides a setter for the parentInjector as well as a dispose() method that we can use to cleanup the module when it is removed. The key here is the setter for the parentInjector that supplies an injector to the module. This setter actually creates the context or the module and passes the injector into the context. As you will recall in the ModularDoodadContext above, the LoggerModule was mapped with the ViewMap. This means that when it is added to the stage its dependencies are injected. parentInjector is marked with the [Inject] metadata so the injector is automatically provided to the module. The context will then use that injector and create a child injector.

Child Injectors

A child injector is a new concept in Robotlegs 1.1. It is a powerful tool. When the parentInjector is set, the module context uses it to create a child injector. This child injector has a reference to its parent and the parent’s injection mappings. This means that if you don’t create a mapping in the child injector it will supply the injection a mapped within the parent (or grandparent). The parent has no reference at all to the child, and doesn’t even know that it exists so if you create mappings in a child injector they are not reflected up the injector chain to ancestors. If you create a mapping that is identical to a mapping within an injector’s family tree, the first mapping is honored and the injector will not check with its ancestors to see if the mapping exists.

Through this mechanism of child injectors we are able to map an event dispatcher within the top level application context that can then be shared amongst any number of other contexts providing that they are able to create and use a child injector. This is the core of how these modules are able to communicate with one another. We create the IModuleEventDispatcher in the shell and all sub-modules now have access to it through their injector. As long as you don’t map a IModuleEventDispatcher with the child, any injections that call for it will be supplied with the original dispatcher from the parent injector.

Now what? For the most part, you develop modules as you would any other Robotlegs application. The Modular Utility does provide one other convenient mechanism to help save you on some typing. That is the ModuleMediator.

DoodadModuleMediator.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class DoodadModuleMediator extends ModuleMediator
{
    [Inject]
    public var view:DoodadModule;
 
    private var madeRequest:Boolean;
 
    override public function onRegister():void
    {
        addViewListener(DoodadModuleEvent.DO_STUFF_REQUESTED, handleDoStuffRequested, DoodadModuleEvent);
        addViewListener(DoodadModuleEvent.REMOVE, handleRemove);
        addModuleListener(DoodadModuleEvent.DO_STUFF_REQUESTED, handleDoStuffRequest, DoodadModuleEvent);
        addContextListener(DoodadModuleEvent.FLASH_YOUR_DOODAD, handleDoodadFlashRequest);
    }
 
    private function handleRemove(event:DoodadModuleEvent):void
    {
        dispatchToModules(new LoggingEvent(LoggingEvent.MESSAGE, "Removing DoodadModule"));
        dispatchToModules(event);
        view.dispose();
    }
 
    private function handleDoStuffRequested(event:DoodadModuleEvent):void
    {
        madeRequest = true;
        dispatchToModules(new LoggingEvent(LoggingEvent.MESSAGE, "DoodadModule made request..."));
        moduleDispatcher.dispatchEvent(event);
    }
 
    private function handleDoStuffRequest(event:DoodadModuleEvent):void
    {
        if(!madeRequest)
        {
            view.color = Math.random()*0xFFFFFF;
            dispatchToModules(new LoggingEvent(LoggingEvent.MESSAGE, "DoodadModule changed color: " + view.color));
        }
        madeRequest = false;
    }
 
    private function handleDoodadFlashRequest(event:DoodadModuleEvent):void
    {
        view.flashIt();
    }
}

The DoodadModuleMediator extends ModuleMediator. The ModuleMediator provides basic injections for the ModuleCommandMap and the ModuleEventDispatcher. ModuleMediator also provides some convenience with a bit of syntactic sugar to make it easy to dispatch events to the ModuleEventDispatcher. The dispatchToModules() method sends events over the ModuleEventDispatcher in contrast to the dispatch() method which sends events over the EventDispatcher that is local to the context. Additionally you are provided with addModuleListener() that makes it easy to add a listener for an event type that is expected to be dispatched from another module (including the shell). These methods simply abstract the eventMap.mapListener() method, which in turn is abstracting eventDispatcher.addEventListener(). You have nothing if not options when it comes to adding a listener within a mediator!

Modular Application Development in Flex… Some Caveats

Possibly the biggest gotcha with modular application development is managing memory. With a typical application you are dealing with a single “module” – the application itself. You do not typically attempt to unload your entire application from memory. Simply refreshing the page or navigating away more effectively does this. We do try to prevent memory leaks diligently (right?) to provide our users with the smoothest possible experience, but this is different from trying to release all of the memory the application uses at runtime. Modules, on the other hand, can come into existence at runtime at any point during the lifecycle of your application. On the flip side of that is that the modules should be able to completely unload and release all of the memory that they have used.

There are a host of “tricks” to getting modules out of memory. This article is a good overview of the major ones. In addition to these sneaky memory peggers, you will also want to carefully ensure that your modules release references and dispose of objects properly.

The Modular Utility makes every effort to provide mechanisms for disposing of your modules, but when it gets down to it most of that responsibility will be left up to you. If you notice that Robotlegs or the Modular Utility is preventing a module from unloading please let me know so that I can address it as soon as possible. The end goal is to provide a robust tool for developing Robotlegs applications with modules. The Modular Utility is currently very modest, but what it isn’t is a solution looking for a problem. As it grows it will be in response to real problems, and your feedback and use is critical to meeting that goal.

The bits you’ll need…

Robotlegs Modular Utility (my fork)
The full source for this example
Robotlegs AS3

On a related note…

I recently had the pleasure of writing a full 22 pages on Robotlegs in the upcoming Flex 4 in Action from Manning. It is a great book overall and if you’d like to learn more about Robotlegs (or Flex 4 in general) then I highly recommend it.

Creative Commons License
The Modular Robotlegs by Joel Hooks, unless otherwise expressly stated, is licensed under a Creative Commons Attribution 3.0 United States License.
  • http://twitter.com/Aaronius Aaron Hardy

    Some of you have asked how to keep the modules in separate swfs (keeping the module code out of the main app) and load them in at runtime. Here's my fork of Joel's sample app:

    https://github.com/Aaronius/ro…

    I load the logger module using ModuleLoader and the doodad modules using ModuleManager so you can see both methods. See the diffs for what changed. The one gotcha is that you have to make sure the LoggingEvent class gets compiled into the main app. Joel, if you get a sec I'd love to hear if you see any improvements to be made.

  • derekrosien

    Hey guys!

    My module's context startup methods are not firing… They all work perfectly within Flashbuilder but do not fire online… 

    I am not sure what is going on but can someone take a look and help me out…

    A more detailed description can be found here 
    http://knowledge.robotlegs.org…

    Online sample of what is going on is here ( the Module 1 & Module 2 buttons should put text into the textarea )
    http://urbanalias.com/test/mod…

    And a sample project can be found here
    http://urbanalias.com/test/mod…

    Thanks for the help.

    Derek

  • jmp909

    for anyone interested, the slow down with multiple doodads appears to come mainly from the console logging not the modular nature. if you remove the console from the app the modules are a lot more responsive even when adding a few. 

    Great example thanks Joel. this and http://labs.riamore.com/content/robotlegs/examples/dynmodules got me up to scratch with RobotLegs & Flex, for some of these more complicated applications of the frameworks, from a beginner in in a few days