Saturday, May 12, 2007

Can Cairngorm and Modules play nice?

Let me say, first off, that my wife gave birth to a beautiful baby girl on Wednesday morning, and they are doing just fine. I've been relocated to the spare bedroom so that I can actually function during the day, however I'm missing about 30 hours of sleep this week that I'll never get back. Having said that, I am blogging today because even though I've sort of fixed my latest issues, I'm not entirely sure I've used the best approach.

This is really a sequel to my previous blog... I am still fighting with getting modules to load properly and independently of each other and the shell flex application. Here's the deal...

I have one complex flex module that is built on the Cairngorm 2.2 framework. In itself, the module works fine, and after the progress from the other day, loading multiple instances of the module into a simple shell works fine too. However, when I tried loading multiple instances of the module into the actual application, things died. Why? Because the shell is built on the Cairngorm framework as well. The minute I add in the <services:AppController id="appController"> line to my shell app, subsequent instances of the module don't load properly. I determined that the shell needed to use a different controller than the module, but that wasn't enough (though still necessary). There were funky things happening again when events were triggered - weird casting errors, events firing across multiple module instances, shared models, etc.

A couple of observations:
CairngormEventDispatcher basically returns a singleton. If the shell app gets an instance of this class, then any Modules that load will use the same instance. So, adding the appcontroller to the application will in fact get an instance of the dispatcher. Running through the debugger, I noticed that as I loaded each new instance of the same module, event listeners were added to the singleton's eventDispatcher. So, say your module was listening for the ContactEvent.LOAD_CONTACT event. If you loaded 3 instances of that module, your controller would execute 3 commands, once for each module.

When an event is received by a command, and you try to cast that event back to its origin, i.e. ContactEvent(event), it will only work in the module that actually originated the event. The other modules will throw the following exception:
" cannot convert com.crazedCoders.moduleTest.event::ContactEvent@4b23281 to com.crazedCoders.moduleTest.event.ContactEvent." Trying to test for it, i.e. if(event is ContactEvent) doesn't work since the event is in fact a ContactEvent. Very frustrating.

If you do not use any Caingorm in your shell application, you will not run into these issues, so for some that might be the quick solution. Sadly I was not afforded that luxury, so I had to find another way.

Since I was trying to treat each Module as an autonomous mini-application, I decided that I needed a CairngormEventDispatcher specifically for each Module and the Application. I created a Factory Class that would do just that. Here's the code:

package com.adobe.cairngorm.control{
import mx.modules.ModuleManager;
public class CairngormEventDispatcherFactory
{
private static var instances : Array = new Array;
public static function getDispatcher(obj:Object) : CairngormEventDispatcher
{
// in order for each module developed using cairngorm to be able
// to work independently of the shell and other modules,
// we need to create a hash map of cairngormeventdispatchers
// that are keyed on module instance or application.
// This prevents cross module listening for events,
// which is beneficial especially when loading multiple instances
// of the same module
var ed:Object = ModuleManager.getAssociatedFactory(obj);
var parent:String = "application";
if(ed != null){
trace(ed.name);
parent = ed.name;
}
if(instances[parent] == null){
var cgDispatcher:CairngormEventDispatcher = new CairngormEventDispatcher();
instances[parent] = cgDispatcher;
return cgDispatcher;
}
else{
return instances[parent];
}
}
}
}
It took me a while, but eventually I stumbled upon ModuleManager.getAssociatedFactory(object). This function *seems* to be able to figure out within the context of which Module (if any) the object was instantiated. If an object belongs to the shell, it returns null, if not, then it returns a ton of information, including a name property that contains a unique name i.e. instance77 for the loaded module. I create a new instance of the CairngormEventDispatcher for each instance and stuff it in the static associative array, then I replace the usual CairngormEventDispatcher.getInstance().dispatchEvent(...) with
CairngormEventDispatcherFactory.getDispatcher(this).dispatchEvent(...));
Now the application shell and each module gets it's own singleton, and the overlap seems to be taken care of.

Again, I welcome any feedback. I'm not particularily fond of this solution, and I hope someone out there can offer a better one.

8 comments:

j said...

Congrats on your daughter!

Jesse said...

Mike Britton and I are having similiar joys loading Flex apps. We are in a super-create mode right now at work, and there are a lot of software endeavors underway. It's been really challenging to find time to breathe and basically assess what Flex software projects will integrate with others.

For the API's, this is a no brainer. Creating portable SWC's works great. For demo's that need to be shown at the drop of a hat, ApplicationDomain has worked great in ensuring whatever I load will work (as long as you don't unload it and then resize the browser).

For bigger endeavors, though, this doesn't facilitate sharing code, and more importantly, saving developer effort. I personally haven't tried using Modules yet with 2 SWF's using Cairngorm. I did try just for the hell of it to use a SWFLoader without he same ApplicationDomain (so the loaded SWF can use the same classes my shell has just like Modules do by default), but that didn't work because the code was using different versions of Cairngorm as well as different implemetations.

Even if we formalized on the same version & implementation of Cairngorm, as far as I'm concerned, we'd still have to do a wrapper class around the default ModuleLoader implementation. It has no idea that I need to have the child clean it up its mess that it left before I unload it.

Thus, for things like Cairngorm, version 2.2 in particular with Command weak references and the ability to call removeCommand facailiate this nicely. Instead of unload just removing the SWF, it also calls a convention method (or an interface if you're that hardcore) that makes sure the child removes all commands, kills any references it has ON ModelLocator if any, and THEN removes itself. To me, I didn't expect ModuleLoader to work out of the box. Cairngorm has dependencies that link things where Modules were envisioned to NOT have dependencies. Both are at odds with eachother, so rather than making Cairngorm play nice with Modules, I'd suggest the other route. Add a new ModuleLoader that takes into account it's loading a Cairngorm aware module.

Easy for me to suggest idealistic ways of working when I myself haven't tried it yet, and run into your casting & event insanities. Too bad you don't work at my place, hah!

Anonymous said...

This sounds like a namespace collision issue. why don't you just type the event using the unique fully qualified namespace?

For module1 ...
class ContactEvent
{

public static var ID: String = ""com.crazedCoders.module1.event.ContactEvent";

...
}

------------------------

in FrontController ...

addCommand( ContactEvent.ID, ContactCommand);

------------------------

any module can do this and you can have multiple cairngorm apps running simultaneously ...

Bjorn said...

Personally i embarked on this a few months ago.

There are a lot more issues and i came across which led me to the conclusion that these 2 dont marry...

Cairngorm + modules, forget about it.

Julian Sander said...

Hi Vic,

first things first: Congates on your daughter and welcome to parenthood!

I am working on a Cairngorm 2.2 app that is using Modules as well.

What I did to solve these issues is to extend the FrontController as a Singelton. I then have access to it from an child ApplicationDomain and can register my commands as well as use the Commands already in place. As I understand the principle of a FrontController to be the only point of contact for the CairngormEventDispatcher I assume it is not a break in standards to make my app specific COntroller work this way. I have also gone ahead and tested this by loading new Commands that are Module specific which works great. Anonymous's suggestion of using a very long static string for the event is a very good one and should probably be used.

For the issue of the ModelStorage I am implementing a system where there is a VO associated with the Module being loaded which I pass refeerence to when I initialize the Module on Stage. This VO can theoretically be defined in the Module itself. The issue here is how the initialization is being done. I am not through the dataBinding issue yet, but I assume I will not hit any hitches there.

So, try a Singelton, it may resolve a majority of the issues.

Just my nickels worth.

cheers, Julian

Thomas said...

I would like to say that Cairngorm and Modules can, in fact, work together. The trick is to compile your shell application and modules separately while using a SWC to include all of the classes to be shared between modules and/or the shell app. You do this by using the -link-report and -load-externs compiler arguments. By using link-report for the shell application you generate a list of dependencies and references. Then using the xml file generated by -link-report, you then use -load-externs for your modules to make sure those same dependencies etc. in the link report xml file aren't compiled into any of the modules. Finally, create a SWC to include the classes to be shared by the shell application and/or between the other modules to be loaded into the shell. I hope that's clear... if it's not just shoot me an email at thomasdfowler at gmail dot com.

Cooper said...

I was able to get a cairngorm swf running inside another cairngorm swf with no overlap of classes or any other type of sharing between the two. I used a SWFLoader with a new ApplicationDomain. The trick, identified here by JLM: http://twelvestone.com/forum_thread/view/39253 , was to include an instance of the Proxy class. I extended the SWFLoader to wrap up the main pieces of code and expose some things of the loaded swf. The only issue, as with anything using a new ApplicationDomain, is casting classes (gotta use getDefinition). But wildcards work fine.

Ranjay said...

We are using Cairngorm 2.2, flex 3and moduleLoader for loading modules at runtime and each module has its own Controller. All modules and main app is compiles separately, main App has no Controller, had issue of event coming as null, even when new Event was dispatched from same module after unloading and loading it.
Fixed this issue by specifying the ApplicationDomain as CurrentDomain in shell on ModuleManager.load call.