For the last year I have been working on a rather HUGE application built in Flex. It is the BIGGEST app I have ever worked on, and it works surprisingly fast given the scope of the project. Part of the reason it functions fast is that:
- I work with some brilliant folks
- The client provides us adequate time to refactor and improve older aspects of the app
- A majority of the app can be broken down into many modules*
It should be noted I will be using the terms data and VO (value object) interchangeably with respect to data objects being sent to/from services)
A common UX paradigm found in our application is as follows:
- user makes requests for data via UI controls (button click, row selection, etc.)
- service supplies flex app with data
- controller makes a clone of incoming data object as a point of restoration via ObjectUtil.copy*
- user enacts changes on cloned data object
- an explicit save request is needed to preserve any user-made changes on the data
It should be noted that we have also created some custom collection types the subclass ArrayCollection*. So everything you’ve read up to this point sounds fairly standard for any sizable application. Normally if you have incoming value objects coming trough on the service and you are using remoting, then you are going to use:
registerClassAlias( "com.foo.Bar", com.foo.Bar );
[RemoteClass( alias="com.foo.Bar" )] public class Bar...
We are doing the registration in a class located in the main SWF (as opposed to the module SWFs). This ensures that the VOs’ class definitions are compiled into the main SWF and that all modules have the same class definitions available to them when they load. Again pretty standard stuff.
So going back to that UX paradigm listed above, remember when I said that we had created some custom collection types subclassing ArrayCollection? Just as common as the VOs are throughout the various modules in the app, so are these custom collections. These objects are not considered remoting objects and by themselves are not candidates for the above treatment (of registerClassAlias and the metadata tag). By not using registerClassAlias on these custom collections, we are electing to have their class definitions’ storage locations be dictated by where they are first declared. If they are not declared in the main shell app, then they will be compiled into each module where they ARE declared, possibly multiple times. This means that if you have ModuleA and ModuleB both using FooCollection, then FooCollection’s class definition will be compiled into each module (assume that ModuleB is not a derivative of ModuleA).
So far this isn’t a big problem so long as a module of a given type is loaded into memory once AND unloaded before another instance of that module is instantiated. Most modular applications don’t make use of a module in a multi-instance paradigm. In our application, some modules are allowed to be loaded multiple times simultaneously. Think of them as forms with different data sets. Let’s call this a multi-use-module as opposed to a single-use-module.
If we have a class definition compiled into a multi-use-module, the first time we call upon that class, it will be stored in memory for later use. Now normally (I assume) unloading a module will unload that class definition from memory. Let’s now create another instance of our multi-use-module. It too tries to call upon that custom class, however because we have a definition in memory from our first module, this will cause a class definition collision in certain APIs. For instance, going back to calling ObjectUtil.copy, which under the hood uses a ByteArray, we run into this issue.
ArgumentError: Error #2004: One of the parameters is invalid.
In order to prevent this error in a multi-use-module scenario, the solution is simple. Simply add you non-VO type classes that are causing the conflict into your main SWF via the registerClassAlias call. This will ensure no class definition collisions occur between multiple instances of the same module or different modules using the same class definition.