Tuesday, April 07, 2009

Remote object reference pooling in Flex

Ever tried to compare object by reference when using remote objects and failed? Or wondered why you have so many instances of the same object in memory.. This blogpost will describe why and when this happens, and offers a solution including source code.

The problem appears when building RIA applications based on the domain driven design principle. Typically you will have a domain layer in your backend, maybe a DTO layer in between, and about the same actionscript version of your domain layer in your client.
Imagine you have a model class in your client containing a loggedInUser:User object and a search view listing all users from the database. Both the loggedInUser and the search results are retrieved through a RemoteObject call with AMF from the backend server. You can select a row in your search view and open a details page of that particular selected user. So what if you want to check if the selected user is the same as the logged in user? Your first try could be something like model.loggedInUser == userForDetailPage and find out this will never work. The solution would be to check on a unique identifier like an id (model.loggedInUser.id == userForDetailPage.id). Just don't forget to also check for null reference (maybe loggedInUser is not set, etc..).

So why is the simple object compare not working? The problem is RemoteObject will give you a new Object() every time you request something from the server. So if you call the method getUserById(1) twice, you will get 2 different objects with the exact same data as a result.

Well that can't be optimal right? It will cost more memory because you have multiple instances of the same domain object and also it will be very confusing... For example if you have like 5 reference to the same domain object scattered across your application, and you change a property in an edit view, the property will be changed on only 1 instance of the 5 instances in your app. The other 4 instances will still contain the old (stale) data, so now you have to refresh the other objects, which again costs more time and memory and results in higher code complexity. The other option would be to reflect the adjusted state to the other 4 objects manually by setting the changed properties.

To fix this weird behavior i created an extension on RemoteObject, called PoolingRemoteObject. This new PoolingRemoteObject adds some extra code that simply checks what object(s) is returned from the server, looks it up in a map and returns the object in the map instead when its already there. This will eliminate the whole multiple reference to the same domain object problem. The original object returned from the server is disposed and the object from the pool is returned.
There is just one problem, if the object is already on the client in the pool, the client will never retrieve a newer updated version of the object. The solution is to just reflection to copy all the data from the new object (from the server) to the old already existing object in the pool. This way the object in the pool is fully updated with the new data from the server. All the 5 references to this object in the pool (as in the previous example) will all reflect the new changes immediately because they all reference to the same object. Changing any of the 5 will result in updating all because it is the same thing actually.

The PoolingRemoteObject also handles collections of objects returned from the server, and recursively handles objects containing references to other objects (or collections of objects for that matter), just to be sure a domain object will never have multiple instances on the client. Logic is added to detect and handle circular references.

This class is already successfully used in production on multiple large flex applications. The only thing you need to do is implement the UniqueDto interface, this interface needs 1 method to be implemented, the purpose of the method is to specify the unique property of your domain object, this usually just returns the id, but could be any other unique property. There also is a Dto interface which you can implement if you do want to scan the dto object for deeper nested UniqueDto objects. When using the Dto interface the object won't be stored in the pool; only scanned. This is handy when you use wrappers around dto objects, for like example a SearchResultDto containing paging info and a List of UniqueDto's. The SearchResultDto won't be stored in the pool (because it isn't uniquely identifiable), but the List inside it will be.

What we also managed to achieve in a very simple way is lazy loading from the client side, using this object reference pool technique. I'll cover more on this topic in a later post.

Here are a couple of code snippets to display how it works, download the zip file at the bottom for the complete source code.

Usage of the pooling remote object:
var service:RemoteObject = new PoolingRemoteObject(method);
service.destination = destination;
service.endpoint = _endPoint;
service.addEventListener(ResultEvent.RESULT, function(event:ResultEvent):void {
//enter your result handling code here as normal...
}
service.getOperation(method).send();
A typical dto in flex:
[Bindable]
[RemoteClass(alias="nl.quintor.sampleproject.web.dto.UserDto")]
public class UserDto implements UniqueDto { 
public var id:Number;
public var username:String;
public var email:String;

public function get uid():String {
return "UserDto@"+id);
}
}
Download the zip file containing complete code: complete code

Credits also goes to my friend and colleague Daniel Rijkhof for helping developing this piece of software.

Update: Daniel discovered a bug in the mx.utils.ObjectUtil class. Apparently the cache inside the object util is not working properly. What they did is cast a Boolean object to a String using the 'as' operator (like: var stringVar = booleanVar is String). This cast always returns null because it isn't a String.
The following bug report is added to adobe's jira: https://bugs.adobe.com/jira/browse/SDK-20516

- Marcel Panse

4 comments:

  1. Great stuff,

    Implementing the pool as an extention on RemoteObject is an excellent idea. This was the last missing piece. Shall we put the code in a google code project?

    ReplyDelete
  2. Shouldn't object pooling be handled by application server instead of having to build this out? Most app servers have advanced object pool management features.

    ReplyDelete
  3. Application servers handle object pooling on a different level, there is not a way to do this all the way to the flash player on the client side.

    BlazeDS simply creates an AS3 version of your Java object, defined by the AMF protocol. The flash player retrieves this AS3 object as a new Object (thus with a different reference).

    Because of this, on the client side this always is a
    different object every time your call the method on the server.

    My class simply makes sure the new object is disposed and garbage collected on the client asap and the flex application keeps using the same object it already had (but updated).

    ReplyDelete
  4. Updated sources url: http://db.tt/7RnrKLP

    ReplyDelete