IVO: Interactive Value Objects in Flash

Posted on January 5, 2011

0


In the past 6 months I created the basis for a design pattern for a structured Data Caching and Data Retrieval system in Flex. As the basic Value Objects were too stupid and not working as a concept I was constantly “bending the rules”. So I decided to drop the “how it is supposed to be” and make this approach into a new standard within the project.

This article deals is one of at least two and deals with the basics of those new rules and patterns.

When you are familiar with the concepts of Object Databases and when you have worked on multi-lingual websites which hot-swap the content and text labels: when you choose another language the stuff below will make some sense.

Value Objects basics

  1. Value Objects are place holders for data – they are created to contain data, not to “do” stuff.
  2. No methods – Except for getters, setters and the constructor, VOs contain no Methods
  3. Passive and do not respond to changes in their data – This is generally done via managers managing the data a VO represents.

IVO basics

  1. I needed a name for the type of specific objects I create – As they are Value Objects, but interactive with the data and the environment that uses the IVOs and after contemplating and discarding alternative names I decided to go for: “Interactive Value Objects”.
  2. Interactive Value Object brief – Extending the concept of the standard VO design pattern with methods to store data to- and retrieve data from the Server and using Cache-mechanisms and Event Dispatchers to optimize code and Server Interaction.
  3. Easy access to multiple instances in the View – The Value Objects are presented in multiple locations in the applications.
  4. Low Event overhead – As some objects might be changed and others not AND we deal with large data sets, I prefer to use the shortest route to the View Objects by dispatching events directly to them from the Data Object that is changed
  5. Simple, self-resolving model – When you delete or change (the content of) a Value Object, it should take all the required actions without you having to go to some meta-layer and tell that layer that object XYZ has changed and should be refreshed. Also, when I look for a specific object with a specific ID, I should be able to do that via the Class of that Object. Again, simplifying the application.
  6. Unified Interface – When you display an object in a list or want to perform a specific action, it should offer an interface consistent throughout your application.
  7. Server side Release – as the system will be used by many users world wide – with heavy loads on the server for each request, each request we can cancel Client side is a win for the Server.

Client Side Object Database in Flash

I will try and work the principles below into a framework that will simplify your life: to register objects, deal with data parsing / data mapping in case of XML objects, build different type of Data Connections to the Server (including BlazeDS, using Socket Connections and simple HTPP polling and Client side Flash Cookies) and query the data Client side.

Stay tuned for that.

If you can not wait: below is the design pattern I will use as a starting point.

Storing stuff Client side or Server side

The framework is designed with persistent Server Side Data (stored in a database) as a starting point. Using Flash Cookies, the database could also be Client Side only. I will take this into the design as one of the possible Data Connections. The main issue with Flash Cookies is that they only offer a limited amount of storing space, up to 100MB per domain / website.

Unified interface

This interface is for all IVOs in Flash.

Loading and saving data from and to the Server

  1. ObjectClass.getListFromServer(queryParameters, successHandler, failureHandler) – gets a list of objects according to the parameters given in the parameters-object, can use a two-phase process, asking for a shortlist of Record IDs to filter out already cached objects before asking all data.
  2. ObjectClass.startBulkOperation() – informs all objects of that class to wait with their operation until they receive the performBulkOperation() call.
  3. ObjectClass.performBulkOperation() – Will start the bulk-operation to the Server. This is especially useful for multiple deletes and multiple updates in case of a action on the client side. Instead of sending multiple calls, there is only one call made to the Server, passing an array of objects to be handled. The bulk-operation works for reload, delete, restore and save.
  4. object.reload() – reloads the content of that object from the Server
  5. object.delete() – deletes the object from the Server
  6. object.restore() – restores the object on the Server – provided that there is a roll-back option
  7. object.save() – saves the content of that object to whatever storage you have
  8. ObjectClass.parseServerData(serverDataObject) – A static function on the Class that resolves the Cached Data Object in Flash / Flex and injects all values

Identifying the data source

  1. ObjectClass.objectType – a Static parameter containing the type of the object. Can be either mapped to a physical table or used in a Switch-statement server side, to abstract Front end from Back end issues.
  2. ObjectClass.dataConnector – a Static parameter containing the object that allows any object in that class to directly connect to the Server (or to a Flash Cookie) and load and save data. It will have basic methods like:
    1. dataConnector.getShortList(myQueryParametersObject, successHandler, failureHandler) – provides a short list of ID’s only, to be verified Client Side. Objects already cached do not have to be loaded.
    2. dataConnector.getList(myQueryParametersObject, successHandler, failureHandler) – Provides a list with all (required) data per object. If the list is specific (like 3 properties out of 60) then more data on that object can be added via a specific reload() call, which will load and inject the missing variables / properties / data fields into the IVO.
    3. dataConnector.saveObject(myObject, successHandler, failureHandler) – sending the object to the Server, dealing with reflection and preparation of the data and either calling the successHandler (that will inject the Server Data Object into the proper data object) or the failureHandler (which will show a popup)
    4. dataConnector.deleteObject(myObject, successHandler, failureHandler) – sending the object to the server to be deleted
    5. dataConnector.reloadObject(myObject, successHandler, failureHandler) – sending the object to the server for its data to be reloaded

Identifying the object Client and Server side

  1. object.dbRecordID – a getter that provides the ID as it is stored in the Database we retrieved it from
  2. object.clientsideReferenceID – a getter that provides a unique reference ID to be passed to the Server when we do a round trip.

Injecting the Server Data if the object exists

  1. object.injectValues(serverDataObject) – Passes the object values to the object we have in cache – this is a tailor made method, different for each type of data provider (XML, BlazeDS objects, your own data transfer protocol)

Subscribing to data that might not be there

  1. ObjectClass.subscribeToObject(dbRecordID, onDataHandler) – Creates a proxy-object – basically an empty IVO with the Record ID to be expected from the Server – before the data is loaded. When the IVO is populated with Server Data, the subscribed objects will automatically be notified. This is useful for (context specific) static content presented in the application: like labels, feedback and text.

Displaying data in lists

  1. object.name – provides a name representing the object. Like “Doe, John” or “Jon Doe”.
  2. object.label – same as object.name
  3. object.ID – a getter providing an ID for use in the front end

Reverting to other versions of the data

  1. object.revertToPrevious() – Acts like an “Undo” and will move through the “change log” of the IVO and recall the previous version
  2. object.revertToNext() – Acts like a “Redo” and will move through the “change log” of the IVO to recall the next version if there is one.

Finding the object by reference

  1. ObjectClass.getObjectByID(objectID) – uses the ID provided via object.ID
  2. ObjectClass.getObjectByRecordID(recordID) – uses the official recordID as was provided by the database
  3. AllObjectsDictionary.getObjectByReferenceID(clientsideReferenceID) – uses the unique client side ID to find the object

You might find getObjectByReferenceID handy in cases where several objects of different types share the same method to call to the Server. In my case to retrieve Analysis Data on Researchers and Clusters (two completely different objects). To offer a recordID is not sufficient as both Clusters and Researchers can have the record ID “1”.

Available event dispatches

  1. OBJECT_DATA_HAS_CHANGED – When the user or someone on the Server has changed the data. As a result the check sum between the old data and the new data differs and this event is dispatched.
  2. OBJECT_DATA_HAS_BEEN_INJECTED – When we injected data from the Server on an existing object. The data is not necessarily changed.
  3. OBJECT_CHILD_COUNT_ARE_CHANGED – When children are added or removed from the Data Object
  4. OBJECT_HAS_BEEN_REMOVED – When the data object is deleted / removed

Pre-emptive Object Mapping – subscribing to data

In the case of labels on forms and GUI elements I know beforehand what data I need. Using the IVO  and the static method subscribeToObject, with the record-ID of the data that will be loaded, data from the Server can be easily mapped to objects in the site.

When the Data Object is already loaded, the result of subscription will be an immediate update of the requester.

Advantages

  1. Simplification for complex event models – Event dispatches, loading, refreshing and saving data is all done on and from the object itself.
  2. Data Object unification – No matter how many times you load the same data-object it will always have only one IVO representing and refreshing it anywhere in your application. See also this earlier article on Smooth Data Injection on how that helps you when using the Flex Tree View
  3. Step by step data loading – Instead of loading all data in one time, data can be loaded on demand. The first request can cover just the name and ID of an object while (on reload() ) the rest of the data can be requested and injected.
  4. Possibilities to reduce data load – Before asking for all data on a specific query the Client Side system can firs check if this data is already present by first requesting a “shortlist”. Data already cached Client Side does not have to be loaded again. Repetitive queries can be reduced and even terminated Client Side when the Server Data requested is relatively static (like in product lists updated every so now and then)
  5. A smoother user experience – as we cache data Client Side, we only refer to the server when we do not have that data yet. Once loaded, requesting that data Client side results in an almost instant response.
  6. Fit for Multi Language sites – By registering IVOs via identifiers to labels and text fields in the application, by reloading the data with a different languageID will automatically update all labels and textfields affected. It will also invite Site Builders to use a database to store language-specific data.

Disadvantages

  1. Overhead – Each object loaded from the Server is first checked against a Lookup Table. This is costly. With a lot of data loaded (more than 2000 records) from the server, this can potentially freeze the application. The solution is to parse this data over multiple frames (each frame is a redraw of the GUI and can be used as a trigger for timed events)

Memory management

When you cache stuff in memory, the price is that memory fills up until it is released.

There some ways to measure and monitor the memory Flash consumes. Unfortunately this is over all Flash movies running at that time. Including banners on other web pages in your browser. Taken this as a given – and knowing only this option for now – the following scenarios are possible:

  1. Mesuring memory consumption – based on measuring memory use on start and while running you can kind of keep tabs on how much you are consuming. Measuring the increase per item loaded can be a rough way to calculate the average size per cahceld object
  2. Using Weak References – see this article by Grant Skinner. This works for Dictionaries and Event Listeners (meaning that even though there is a reference to objects, the garbage collector will remove them).
  3. Define a limiter for caching – Using the principles of a stack, limits on the amount of objects per stack and a mechanism based on “not recently used = first to be dropped” you can set limits on your cache. When the maximum amount of items per stack – or some other limit – is reached, you start to either recycle objects, or drop them completely: to stay under the limit. This limiter can be used for data objects, but also for cached images and media files. Especially for Tablets and mobile phones this approach might prove to be very relevant.
  4. Dynamic memory limits by approximation – A desktop with 4 GB of RAM will reach the system limits much later than a smartphone with 256 MB. One way to simply find out what you are dealing with is the screen real-estate. For now, systems with a screen width equal to- and less than 1024 pixels can be considered to be low end machines and thus low on memory. Meaning 1 GB and less. For current 2010 tablets and smartphones running Flash, the total RAM can be considered to be 256 MB. Which leaves about 128 MB for Flash if you play it nice.
  5. Recycling – I mentioned this option before. With the risk of retaining objects in memory due to strong references (i.e. the object is still referenced in some Array or variable / property) and with a unpredictable garbage collector Recycling gives you some control over the amount of objects used. This is how it works: instead of creating a new object for each thing you add, you first check the “trash bin” for objects that have been removed. I have this working for display objects / MovieClips in lists. Flex also makes (overtly) use of this principle for the same reasons. It works. It is more robust than “hoping that the garbage collector can remove the object”.
  6. The dispose() method: What I miss in AS3 is the option to kill an object. A method that forces the object to be removed from memory and from all bindings. Based on the settings in the Class that is referred, dispose() will either place the object in the trash – for recycling – or will attempt to break all bindings and offer it to the Flash garbage collector
  7. Extending Array, Vector and your own Classes – With Dictionaries and Event Listeners out of the way, Arrays and Vectors (and ArrayCollections in Flex) are one of the big remaining pains. To solve this, objects you want to remove should get knowledge of where they are referenced and then be allowed to kill that reference. By extending the Array and Vector classes in such a way that they register themselves against your objects (so there is a double binding going on) you can force both (the Array and Vectors) and your own Objects to erase their mutual references.
    Note 1:
    creating double bindings and executing “kill” or “erase” methods on objects will create extra overhead that was not there before.
    Note 2: What I like about extending Arrays is that I can add some extra coolness to it like: “remove object B from the Array” which currently is a copy & paste solution I use anywhere required.

XML

With AS3, XML became the native environment for data mapping. As much as I would like to stick with XML, XML is too “open” for some OOP practices. What I have in mind for XML is this:

  1. IVO with Object reference to XML node – The IVO will contain the reference to the specific XML-node
  2. Using Getters and Setters – Using getters and setters you can access that node as a regular object, including offering you reflection in the coding environment. This saves us from parsing the data and the overhead that parsing will produce.

Data injection in XML nodes

Like objects from BlazeDS, each time I load an XML structure, it will contain a new Object Model with new Objects. Even though some of these Objects are already loaded before, the XML object model has no awareness of that. To wire these objects to the “single object representation model” we need to resolve these new XML nodes to their existing counter parts in our Object Model and Object Database.

Resolving object references by using Data Multicasting

One solution to keep references to all XML objects representing the same Data Object is to store all XML references we find into the IVO when parsing the data. So when “object 12” is loaded 20 times from the Server, it will have 20 objects and 20 object references in our IVO.When we change the values of a node via our IVO, it will update all instances of that object in all XML nodes, assuming that when we send an XML structure back to the Server to be processed there, it will be up to date instead of “version 1 of 10 successive changes”.

Updating XML objects – overhead issues

When we load new data that contains updated Data Objects already stored in other XML nodes, we HAVE to pass this data. One way might be to inject our data into the existing nodes we registered. This will create overhead issues. Especially when we have large data sets.

Cleaning up the cached XML objects and strategies to reduce overhead will be an entire new chapter. So we stop here for now.

Undo / Redo change

When the user changes data, it might be beneficial to have an undo-functionality. As we use IVOs which allow us to do more than just storing data, we can implement a nested cache containing a change-history of the Data Object. This will be per IVO. Caching over several Application View State, using an object stack (that can contain multiple instances of the same IVO) will be a separate chapter.

Setup – an example

This example is to give you an idea of how a Interactive Value Object might be setup. It might be updated when I find better and / or more efficient ways to parse the data.

Most important aspects:

  1. Parse our Server Data Object – via a static function parseServerData on our Class
  2. Check the Client side Cache – When we parse through the Server Data, we check if the object was already loaded in our Client side Cache
  3. If so: return the Cached object – Behind the scenes we
  4. When all done: process the updated Value Objects – When each “updated” Value Object is processed, an event is dispatched form that Value Object, notifying all (View) Objects with a listener that something has happened to it.
public class MyIVO extends BaseIVO implements IBaseIVO {
    // The lookup table
    Static var myLookupTable:Dictionary = new Dictionary();
    Static var myInjectedObjects:Array=new Array();

    // The most recent injected object
    var intjectedObject:MyIVO=null;

    // This is mainly handy when you use BlazeDS
    public function set myRecordID(id:int):void {
        // Store this object in the lookup table
        // if it not exists
        var object:MyIVO=myLookupTable[id];

        // It does not exist yet - this is 
        // now our main object
        if(object==null)
        {
            myLookupTable[id]=this;
        }
        else
        {
            // We need to process the injection
            // When we are done processing the data
            object.injectValues(this)
        }
    }
    // Constructor
    public function MyIVO():Void {
    }

    // "Advanced feature" - We call this
    // when processing the Server data from XML or
    // another object
    public static function parseServerData(serverDataObject):MyIVO {
        // Do stuff here to get the base IVO or create a new object
        myIVO=getObjectByRecordID(serverDataObject.@recordID)

        // if null, create new 

        // Inject the values from the Server Data Object
        object.injectValues(serverDataObject)

        // Process the data injection
        processInjection();

        // Return the IVO
    }

    public static function getObjectByRecordID(recordID:String):MyIVO
    {
        // return the IVO, or a null-value
    }

    // IBASE IVO
    public function injectValues(serverObject:SomeClassType):void{
        // Do nothing now, as we have not all values
        myInjectedObjects.push(this)
    }

    public Static Function processInjection():void
    {
        // Here we finalize the data injection
        // Do stuff to copy values from Server Object to this object

        // Loop through all updated objects
        // Dispatch event on each object - Data Object changed
        myInjectedIVO.dispatchEvent("OBJECT_DATA_HAS_BEEN_INJECTED")

        // Clean the array with the updated objects
        myInjectedObjects=new Array();
    }

    // Unified Interface
    public function get ID():String {
        return String(myRecordID) ;
    }
    // And so on
}

Having your View Objects listening to the Data Objects

Advertisements