views:

94

answers:

2

Suppose you have 2 classes, Person and Rabbit. A person can do a number of things to a rabbit, s/he can either feed it, buy it and become its owner, or give it away. A rabbit can have none or at most 1 owner at a time. And if it is not fed for a while, it may die.

Class Person    
{    
    Void Feed(Rabbit r);    
    Void Buy(Rabbit r);
    Void Giveaway(Person p, Rabbit r);     

    Rabbit[] rabbits;    
}

Class Rabbit
{
   Bool IsAlive();    
   Person pwner;
}

There are a couple of observations from the domain model:

  1. Person and Rabbit can have references to each other
  2. Any actions on 1 object can also change the state of the other object
  3. Even if no explicit actions are invoked, there can still be a change of state in the objects (e.g. Rabbit can be starved to death, and that causes it to be removed from the Person.rabbits array)

As DDD is concerned, I think the correct approach is to synchronize all calls that may change the states in the domain model. For instance, if a Person buys a Rabbit, s/he would need to acquire a lock in Person to make a change to the rabbits array AND also another lock in Rabbit to change its owner before releasing the first one. This would prevent a race condition where 2 Persons claim to be the owner of the little Rabbit.

The other approach is to let the database to handle all these synchronizations. Who makes the first call wins, but then the DB needs to have some kind of business logics to figure out if it is a valid transaction (e.g. if a Rabbit already has an owner, it cannot change its owner unless the Person gives it away).

There are both pros/cons in either approach, and I’d expect the “best” solution would be somewhere in-between. How would you do it in real life? What’s your take and experience?

Also, is it a valid concern that there can be another race condition the domain model has committed its change but before it is fully committed in the database?

And for the 3rd observation (i.e. state change due to time factor). How will you do it?

+1  A: 

There are a couple of issues to think about here which should help you come to a design:

  • In reality, does a rabbit need to know who its owner is? Meaning, does it need a reference? If you're trying to model the domain, it's probably unlikely a rabbit knows who its owner is (and what if it's shared by two people)?
  • Should a person have a reference to a rabbit directly? Or do you think it makes more sense to have a more generic interface, like an Animal? It doesn't seem like these operations are rabbit specific?
  • With regards to the threading and synchronization, is your application multi-threaded? Is it possible in your app for two people to try to be the rabbit's owner at the same time? If you were modeling a pet store, for example, it wouldn't really be possible for this to happen (also if a Rabbit didn't have a reference to its owner, this concern may not exist anymore).
  • If both objects really need references to each other, I would probably do the synchronization at the object level (with locks). Since you may update the domain model in memory and then persist it to a database (maybe on app shutdown or something if it's a desktop app), you always want memory to be in a consistent state.
  • In general, start with the simplest solution and refactor as needed. Given your question and where you are in development, I find it very unlikely that a difference in synchronizing at the object level or at the database is likely to be a performance issue.
Jeff Storey
+1  A: 

1, it may or may not be practical for the rabbit to know its owner, in certain contexts animals do know their owners by tags or microchipping.... in DDD you model what makes sense in the context of your specific domain. But its ok for them to know about each other. You will want to make locks for transfer of ownership. Thats relatively simple with most ORMs

3rd. Surely a Owner owns a dead rabbit? But its simply enough as part of killing the rabbit you remove it from its owner. As for what kills the rabbit, perhaps you need class GrimReaper that is scheduled in a timely way to look at all ILivingThings and works out if the conditions for life have been sustained, if not ILivingThing.Slaughter().Reap();

Keith Nicholas