views:

95

answers:

2

We have a complex aggregate (sensitive names obfuscated for confidentiality reasons).

The root, R, is composed of collections of Ms, As, Cs, Ss. Ms have collections of other low-level details. etc etc

R really is an aggregate (no fair suggesting we split it!)

We use lazy loading to retrieve the details. No problem there.

But we are struggling a little with how to save such a complex aggregate.

From the caller's point of view:

r = repository.find(id);
r.Ps.add(factory.createP());
r.Cs[5].updateX(123);
r.Ms.removeAt(5);
repository.save(r);

Our competing solutions are:

  1. Dirty flags Each entity in the aggregate in the aggregate has a dirty flag. The save() method in the repository walks the tree looking for dirty objects and saves them. Deletes and adds are a little trickier - especially with lazy-loading - but doable.

  2. Event listener accumulates changes. Repository subscribes a listener to changes and accumulates events. When save is called, the repository grabs all the change events and writes them to the DB.

  3. Give up on repository pattern. Implement overloaded save methods to save the parts of the aggregate separately. The original example would become:

    r = repository.find(id); r.Ps.add(factory.createP()); r.Cs[5].updateX(123); r.Ms.removeAt(5); repository.save(r.Ps); repository.save(r.Cs); repository.save(r.Ms);

(or worse)

Advice please! What should we do?

A: 

So, if you don't wan't to use an ORM, you have to build your own. You can name it repository, but in fact this would be an ORM.

Solution 3 is against the idea of repository, so I would advice not to use it. How about saving in the Unit of Work state of the aggregate in the moment of loading from database and later, during commit, comparing it to the current state? You could get rid of all the dirty-marking stuff at the cost of storing some additional data in memory.

Szymon Pobiega
A: 

Solution 1 (using dirty flags) is not appropriate because you end up with your persistence logic leaking into your Domain Model. Your Domain Model shouldn't care about the persistence: what is changed, what is added, so on.

In Jimmy Nillson's book I noticed the way he handled this scenario was that everytime the aggregate was to be persisted, the aggregate root was updated, all of its children were deleted, and then each were inserted again.

This is a fairly easy solution to implement, however it has some implications. For example, everytime you re-insert a child object, its ID may be changed. What if another user is editing the same aggregate at the same time?

Has anyone else come across this issue before?

Mosh

Mosh
The fact that a child object's id could change should not be a problem since child entities should not have global identity.
Think Before Coding
Please consider this example: A user may want to remove OrderItem 11 belonging to Order 1. This can be achieved by something like GetOrder(1).RemoveItem(11). At the same time, another user may be updating Order 1. So, re-inserting Order 1 with all its children may result in the ID of the OrderItems to be changed. So, in this case, OrderItem 11 may become OrderItem15. So, the call to GetOrder(1).RemoveItem(11) will fail. Is there something I am missing? Please clarify.
Mosh