My first impression is that whenever you need transactional consistency between aggregates, you wrongly designed your aggregates.
That's exactly right; the way that you discover aggregate boundaries is first by identifying values that need to by kept consistent, and then choose boundaries that have the property that any two values that need to be consistent with each other are within the same boundary.
Note: we don't always get this right; perhaps the requirements were wrong, perhaps our model wasn't adequate, perhaps the business changed. Part of the challenge is to make sure that it is always easy to replace the current model with a better one.
Based on this information you can create three aggregates, Cow, Stock and Order.
Note: Cow
is a lousy aggregate -- assuming we are talking about real out in the world eating grass cows. It's an entity, yes, but it is outside the influence of the model. If the model says that the cow is empty, and the cow says that it is full of milk, the cow is right.
Similarly, if the cows are real, then most of your stock is real as well. If the model says there are seven full canisters of milk, and the farmer counts six, then six is the right answer.
Aggregates are information resources.
Whenever a certain amount of milk is ordered, one of the business rules is to check if that amount is in stock and if not, let the user know right away. How can this be achieved when two users do a concurrent request and order a total amount of 150 liters milk, while only a 130 liters are available?
An important thing to realize about "right away"; you are here, the user (buyer?) is there. There's a certain amount of latency in the communication, which means that the information you are sending to the buyer is already stale when it arrives. (Technically, it's already stale at the point when you send it.)
Second fulfilling the orders requires understanding both orders and available stock. So you might model this as a single aggregate that does everything, or you might model it as order aggregates that communicate asynchronously with some fulfillment aggregate; for instance, it might be that what Stock really does is match up notifications of available milk with pending orders.
In your concurrent processing example, this would look like two commands to reserve 130 liters of milk running concurrently against a stock aggregate with 150 liters of milk available. Using optimistic concurrency both commands would discover that there is enough milk to satify the order, and would try to update the book of record. That update, however, is serialized -- think transaction, or compare and swap -- so one of the commands succeeds, and the other gets a concurrent modification exception instead. This second command therefore tries again, reloading the stock in its new state. This time through, it discovers that the available stock is insufficient, and acts accordingly (moving the order into the waiting list, advising the buyer of the expected fulfillment date, etc).
Note that you would also get concurrent modification exceptions when a command to reserve milk is run in parallel to an announcement that more milk is available.
Cow
andStock
aggregates into one aggregate? It seems these are the parts you need to keep consistent all the time. In my opinion, there is no need to model aCow
as a separate aggregate. – UnionistCow
andStock
into one aggregate, since the business requires this to be consistent. – Tal