Keeping consistency between aggregates
Asked Answered
T

2

5

I am wondering how to solve transactional consistency problems between aggregates. My first impression is that whenever you need transactional consistency between aggregates, you wrongly designed your aggregates. However, I would still like to ask this question to make sure I am not missing anything.

Imagine you sell cow milk. You have multiple cows, each producing a certain amount of liters milk a day. You need to have a service that is able to get the amount of milk in stock. Besides that you should also be able to order milk. Based on this information you can create three aggregates, Cow, Stock and Order. 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? My first idea is that you can achieve this by optimistic/pessimistic locking, but in that case one aggregate depends on the other. Is there a certain way to solve this particular issue, or is this just bad aggregate design?

Tal answered 4/4, 2017 at 19:35 Comment(5)
Maybe you should merge your Cow and Stock 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 a Cow as a separate aggregate.Unionist
So this confirms my thought of this being bad design. It indeed makes sense to merge Cow and Stock into one aggregate, since the business requires this to be consistent.Tal
It's just my opinion based on the brief description you provided. If your domain is a "real-world" problem, it might be much more sophisticated and you should definitely iterate your model many times with your team to find the best option. However, if you are satisfied with my comment I can make an answer out of it, so the question is not marked as "unanswered".Unionist
I realize that. I was mostly looking for the confirmation that whenever you face the issue that you want transactional consistency between multiple aggregates, your design is wrong. I'm not sure if this short example proves that point completely, but it seems valid to me.Tal
In such case I'll leave some links to Vaughn Vernon's materials regarding aggregates modeling. There are situations, where you are forced to create a transaction spanning across aggregates - but these should be definitely avoided, as they defy the whole aggregate idea. Here is the link - I find these articles really good dddcommunity.org/library/vernon_2011Unionist
P
5

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.

Pulsar answered 4/4, 2017 at 20:40 Comment(0)
D
3

My first impression is that whenever you need transactional consistency between aggregates, you wrongly designed your aggregates.

I'll go the other direction here and say: not necessarily. You are always going to require consistency between aggregates at some point in time. The only question is when. You may find that you run into some politics in some situations. I have had to implement 100%, immediate consistency, between a whole bunch of aggregates simply because I was instructed to do so by those holding the purse strings. In the ideal world we'd be free to choose. But in my real world you can bet we're going to lose. Anyway, lyrics aside, it certainly is possible to get away from immediate consistency.

Altering more than one aggregate in a transaction isn't the end of the world but if you can avoid you certainly should. It does come with some slight overhead as can be expected.

In your example you can check the stock levels but, as noted, concurrent reads lead to misleading information. However, you could place an order and before you even kick off your process you could "reserve" the quantity using what will become your process manager's Id and your overall "correlation id". if you cannot reserve the stock you could abandon the order. However, you'd have to reserve multiple items for certain orders with more than one item.

There are quite a number of approaches to this (airline reservation can also be used as an example). You may want to take the order anyway and see what you can do. Order may have different turnaround times and more milk, or other items, may become available in the meantime. You could notify the client that their order could not be filled and has been cancelled or inform them to click a link to adjust the order or perhaps accept an alternative.

Just some thoughts :)

Douceur answered 5/4, 2017 at 10:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.