Brian's Waste of Time

Fri, 20 Jun 2003

So I was thinking about the problem of easily making value object graphs in a conveient way and decided that it should be automated at runtime. Value Objects are basically just objects that are serializable and can be easily passed across application boundaries. The idea is that it is a course grained object with lots of info - minimizing network calls.

The problem is that this runs really counter to good OO programming, where you want the relationships between objects (composition) to be meaningful. Building a VO then becomes an additional mapping process, ick. You want to use the VO though as it is very convenient for passing across application boundaries - whereas a whole complicated group of small objects are decidedly not good to marshall across a network.

Sun advocates using a Value Object Assembler to build these graphs - which is great - if you have a good one or don't mind coding one. Typical implementations of this turn into a rats nest maze of a Bridge before an application is even released.

So, lets look at a domain model (read only) set of interfaces for an online bookstore which we would like to publish to clients.

public interface User { String getHandle(); /** returns a set of Book instances */ Set getSuggestedBooks(); } public interface Book { Author getAuthor(); String getTitle(); int getNumberOfPages(); /** returns Page instances */ List getPages(); } public interface Author { String getName(); /** Returns set of all books by this author */ Set getBooks(); } public interface Page { int getPageNumber(); String getText(); }
You might have a User instance cached in a session context. You now want to render in html a list of suggested books. What the model behind the scenes does is a really complicated statistical analysis of the books bought, viewed, known demographic data, etc and generates a set of five books it thinks this user would like. As we are good little J2EE developers these Book instances are really EJB Entity Beans - a really heavy, non-reentrant, remotely marshalable object. If we return a set of references to five of these puppies, every call to it will in fact be a call to a remote object and involve a network roundtrip. Everyone knows this is bad. However, we have a good object model we want to expose to the client... so....

Vole comes in. Vole will create an immutable, serializable clone of the entire object graph (in this case a Set of object implementing the Book interface, and all of the properties on those objects - such as the Author, the Page instances, etc). This object graph is lightweight, is value oriented, and does no actual calculation - just returns values.

Vole's process is to generate instances of the interface at runtime and set the value to be returned by the function to whatever the source object evaluates to (so if it is a lengthy and expensive operation, it'll be snappy to the user of the Vole graph, etc). It knows about a finite set of interfaces it can implement (the ones defined above for example) and will only copy those interfaces plus core Java ones (String, Integer (as Integer, and as an int wrapper), Map, List, Set, Collection, BigNum, etc). As it keeps an identity cache of objects it has created for a particular graph it can handle circular references (the Book -> Author -> Book cycle above) etc.

It adds Serializable to anything it creates (and as it has to eventually break down to Java primitives or empty interfaces, this is easy to do). Additionally, it needs to proect collections from modification - so add(), remove(), put(), etc need to throw OperationNotSupported exceptions (legal for Collections API) and voila.. immutable value object grapgh to send across boundaries which represents a snapshot of the relevent subset the domain model at the time of the request.

It gets better though - using magic naming conventions (and supporting non-magic naming) we can map objects to interfaces dynamically.

We have a mapping file, prolly xml as it is conveniently human readable and hieracrhical) as follows:

<vole:repository> <vole:node source="domain.Author" interface="Author" /> <vole:node source="domain.Book" interface="Book" /> <vole:node interface="Page" /> </vole:repository>
Look at the Author mapping above. The source indicates a class to take data from, the interface the interface to implement. In practice it will do something like:
/*****/ interface.class evaluate(interface) impl = new implemention of interface with setters available here foreach ( method in interface.methods ) result = interface.evaluate(method) if (result is a mapped interface or Java 'primitive') impl.set(method, (cache.contains(result) ? cache.get(result) : evaluate(result))) else impl.set(method, { throw new UnsupportedMethodException() }) next cache.add(impl) return impl /*****/
Deep value cloning =)

Where no source is specified there is an implied source the same as Page - so Page implementations are mapped to Page interface instances.

Now, the fun part - because systems evolve and we want to make it easy for clients - you can do method level mapping as well.

To revisit our Page mapping:

<vole:repository> <vole:node interface="Page" source="domain.FancyNewPageImpl"> <vole:method target="getText()" source="getContents()" /> </vole:node> </vole:repository>
or maybe
<vole:repository> <vole:node interface="Page" source="domain.FancyNewPageImpl"> <vole:property target="text" source="contents" /> </vole:node> </vole:repository>
As JavaBean type access is all the rage lately. Probably it will support both since we want to be able to map non JavaBean compliant API's as well. This type of thing is only necesary when method names do not match, but allows for domain model API to evolve differently from client API's.

Anyway, that is the idea behind Vole.

0 writebacks [/src/java/vole] permanent link