19 October 2005

Object modeling to code: associations as first-class citizens

I had this feeling for a long, long, long time. I always found that object modeling in UML helped giving birth to what Eric Evans calls the "ubiquitous" domain language (in his book "Domain-Driven design"). As a consequence, I thought that object programming languages were really missing an "association" type of attribute that would convey the kind of meaning you have in your class diagram.
This was just the kind of idea that could be somehow implemented someday in java10 after extensive academic research (that was certainly going on the subject - isn't it the classical way of promoting new programming languages?).
Anyway, all this was just day-dreaming and practically speaking I was more or less mentally mapping the object business model to my java classes. Alternatively, I used Together's java annotations to describe the collections.
But we are now facing a though challenge that many people must have dealt with in many places (the question is: who and where - please email me,...) : how can you evolve an existing graph of objects from an XML file that is supposed to represent its next state. This is not so easy because:
  • if an object is not present in the XML anymore, it has to be removed from the object graph everywhereit is used, and due exceptions must be sent if it shouldn't be allowed. Failing to do that properly would cause nasty errors in our object database
  • changing the state of objects can have various impacts on other objects in the graph and those impacts can trigger a "chain-reaction" from the bottom of the object graph to the top
My first idea was to have a metamodel of my application describing all the relationships between the objects: who is referencing/composedof who through a collection (well, our first idea was to use an Observer design pattern throughout the code).
Then I would use this metamodel for several things:
  • generically produce the XML equivalent of the business objects
  • computing differences between the "state before" and the "state after"
  • sending the differences to our internal "EventBus"
  • generically having the business objects subscribing for the changes that are applicable to them
  • let the business objects do their job and send other events for the possible impacts
Getting down to the details, this design seemed to fail for a fundamental reason: you have to declare some knowledge twice in the application:
  1. when you create your classes, they have relationships through pointers even if all the semantic of the association is not declared
  2. when you create your metamodel, you add more information to the existing one
And this week-end, I had "the" idea: why not add the missing information to the code itself?
So I decided to create Association classes that would satisfy all our requirements for minimum redundancy, maximum robustness and distributed behavior among classes:
  • ZeroManyAssociationList/Map (implementing List/Map)
  • ZeroManyCompositionList/Map
  • OneAssociation
  • ZeroOneAssociation
  • ...
And those association containers would be observers to the objects they contain.
  • when a business object is removed, all the containers are notified and can cleanly remove their reference to that object
  • a OneManyAssociation can check that the lower multiplicity is respected
  • a Composition association can now be used generically to know what to do in case of the removal of the owning object
  • an association can be constructed with its owner and warn him that its content has changed
  • ...
There would be more things to say about this design and how to implement it properly but I just want to end with 2 drawbacks:
  1. The type of a ZeroOne or a One Association is not the type of the referred object. It could be interesting to use a dynamic proxy here, however I don't think that it would work with JDO (the database API we use)
  2. Business objects diagrams with Together will be unnecessarily cluttered by those container classes.
Yet, as for any design where you have to decide between conflicting constraints, I think that if this solves elegantly our need for a more robust object model, this is really worth the price.