17

Quoting from Vaughn Vernon:

When two or more Aggregates have at least some dependencies on updates, use eventual consistency.

enter image description here

He further goes on to suggest that one could make use of Domain Events to publish actions to the other Aggregate Roots that need to be updated.

He further proceeds to explain that Eventual Consistency might be a necessary evil which is in stark contrast to ACID criteria; a set of properties refined over the years to guarantee data validity.

What makes updating multiple Aggregate Roots in one request/transaction such a bad practice? Why should I give up strong ACID?

nicholaswmin
  • 1,869
  • 2
  • 18
  • 36
  • 2
    It sounds like he's talking about a *distributed system*, where each aggregate root might live in a different location. – Robert Harvey Aug 22 '17 at 17:36
  • Database contention. Segregation of responsibility. Tighter coupling. Also, what the guy above said. – Machado Aug 22 '17 at 17:37
  • 2
    The Aggregate is by definition the transactional boundary. – Constantin Galbenu Aug 22 '17 at 18:17
  • It also makes code simpler and thus easier to follow. – Andy Aug 22 '17 at 19:16
  • @ConstantinGalbenu Sometimes in the quest to break up AR's to smaller ones, we have to update 2 AR's based on a user action – nicholaswmin Aug 22 '17 at 20:07
  • @NicholasKyriakides Yes, we do but if we correctly design our aggregates then, whatever happens, the aggregates are always in a consistent state. In the worst case (hardware failure after the first command) the entire system is in an inconsistent but detectable+recoverable state as a whole. That is thd beauty of DDD, you can recover from any failure if you correctly design your aggregates by idenyifying the real transactional boundaries. – Constantin Galbenu Aug 22 '17 at 20:18
  • @ConstantinGalbenu But how would I update the 2nd AR in the first place since I'm not supposed to do so in the same request? – nicholaswmin Aug 22 '17 at 20:32
  • 1
    @NicholasKyriakides you can do it in the same request but you should be prepared for failure so you need to have a mechanism to detect failure. – Constantin Galbenu Aug 22 '17 at 20:42
  • @ConstantinGalbenu Ok, so what's the problem in updating 2 AR's in one _transaction_, If the system is not a _distributed system_ ? What problem is Vernon trying to avoid with this 'rule'? Or is this rule strictly about _distributed systems_? – nicholaswmin Aug 22 '17 at 21:00
  • Vernon is not thinking about distributed systems. He's thinking in atomic transactions. Many, a lot of small transactions instead of a huge one. He wants you keep isolated the little "boundaries" that every AR represents. It's named decoupling. It reduces the complexity related to changes. Is easier to understand (and manage) little and well narrowed changes. Decoupling has 2 good side effects: one is high cohesion and this lead us to the second single responsability. If a.command addressed to change 1 AR causes changes on more ARs, that command has too many responsability. – Laiv Aug 22 '17 at 22:23
  • Btw... You can have as many transactions per requests as you need. – Laiv Aug 22 '17 at 22:23
  • @Laiv Thanks for the first comment, Now for your second - Quoting from Vernon: `referencing multiple aggregates in one request does not give license to cause modification on two or more of them` – nicholaswmin Aug 22 '17 at 22:25
  • Also, how do multiple, independent transactions reduce complexity? If one of them fails and the rest succeed, the system is left in an inconsistent state. You could argue that If one fails I could retry it - What if it fails again? What if the error reporting system that logs failed transactions fails as well? Don't I need to build, run and maintain this 'event' signalling system as well now, on top of the other code? - Yes I know, too many questions - but giving up all-or-nothing ACID on all parts of a request sounds more complex and less reliable by a large degree. – nicholaswmin Aug 22 '17 at 22:29
  • Keep reading. The PDF you shared in the former question, he says that in certain scenarios, this is allowed. The question is if this is your case or not. DDD per se is addressed to deal with complex domains. I could understand that small transcations helps to track how a complex model change its state. IMO this is what he is trying to keep in mind all the time. How to keep things (meant to be complex) as simple as possible. Of such simplicity doesn't works for you, you should feel free of doing what works for you. – Laiv Aug 22 '17 at 22:35
  • Ultimatelly, Vernon's DDD is only Vernon's understanding of DDD, not the 10 commands. A proof of this is that he also find exceptions for his own rules ;-) – Laiv Aug 22 '17 at 22:36
  • To be honest I would really love to see the point in what Vernon is suggesting, but I haven't been convinced at all as to the what makes eventual consistency better than transactional consistency even in one case. I've yet to see a concrete example of someone explaining this. Pardon me, but saying that 'decoupling' and 'high cohesiveness' is better than all-or-nothing, always reliable state changes across the model, doesn't really convince me. Care to share a detailed answer, demonstrating an example? – nicholaswmin Aug 22 '17 at 22:43
  • Eventual consistency doesn't only require more infrastructure code, it's also more error-prone than transactional consistency (yes, small transactions are more likely to succeed, but if one doesn't, the errors it produces are more dangerous since they lead to inconsistent state). What benefits does it reap is my question, because it's costs over transactional consistency are not insignificant at all - Keep in mind that I'm questioning it because I don't know and I'd love to see the point, because I'm sure there's one there but I'm missing it. – nicholaswmin Aug 22 '17 at 22:45
  • @Laiv [This video](https://www.youtube.com/watch?v=7kX3fs0pWwc) mostly answered my question - and the folks above are right, this 'rule' was, most probably, set forth primarily to facilitate _distributed systems_, i.e a microservice per Aggregate, since transactions can't span microservices – nicholaswmin Aug 23 '17 at 01:25
  • I have read the three [PDF](https://vaughnvernon.co/?p=838) and In no way I see what distributed systems have anything to do with this question. If Vernon's DDD seems easier to bring to MS is because his DDD, as I said, thinks in high cohesion and loose coupling. Whether is a single and huge monolith or 1000 nano-services (a no sense) doesn't matters. Anyways, I suggest read the PDFs (foot notes too). Specially pag 5 and 6. – Laiv Aug 23 '17 at 02:02
  • PDF1. Pag 5: `This doesn't address the fact that some use cases describe modifications to multiple aggregates that span transactions, which would be fine. A user goal should not be viewed as synonymous with transaction. ` – Laiv Aug 23 '17 at 02:03
  • 1
    Thanks - I already did - _Scalability_ in Vernon's PDF while not explicit, is meant in the context of _distributed systems_. Otherwise the transaction rule doesn't make any sense. The words "cohesion"/"loose coupling" mean nothing in the context of a transaction rule unless there are underlying concerns, which exist in a distributed system. – nicholaswmin Aug 23 '17 at 02:43
  • 1
    Otherwise, there's absolutely no reason to introduce complex _eventual consistency_ mechanisms to ameliorate the 1 transaction per aggregate rule. ACID is almost never sacrificed in favor of "loose coupling" unless there are concrete reasons for it, the inability of transactions to span multiple databases being one of them – nicholaswmin Aug 23 '17 at 02:52
  • 2
    This rule helps you think again about your design when you need a multiple-aggregate transaction, as this is a strong indication that you have not correctly identify the aggregates boundaries. If you have already thought again and again, have gained suficient knowledge about your domain, domain experts cry when they see you again and run in all directions and still need multiple aggregates in the same transaction and you don't need scalability then do it. – Constantin Galbenu Aug 23 '17 at 05:20
  • 1
    @NicholasKyriakides I have been looking for Vernon's articles and interviews and finally I have found the one Vernon's does references to this subject and also answers your other question (to one with a bounty). I have to admit that I was wrong. VoiceOfUnreason is right. Here the [interview]. Hope It helps :-) – Laiv Aug 24 '17 at 07:30

2 Answers2

16

What makes updating multiple Aggregate Roots in one request/transaction such a bad practice?

The problem is the other way around - trying to modify multiple aggregates in a single transaction is an indication that you haven't modeled your aggregate boundaries correctly.

Put another way: modifying two different aggregates in the same transaction introduces a constraint on their storage (the aggregates need to be stored in the same database), and that constraint is not reflected in the model. It's effectively implicit.

VoiceOfUnreason
  • 32,131
  • 2
  • 42
  • 79
  • 1
    Thanks, I've just finished [this video](https://www.youtube.com/watch?v=7kX3fs0pWwc) which effectively goes in detail about the points you've made above. – nicholaswmin Aug 23 '17 at 02:57
  • 1
    [An Interview with Vaughn Vernon on Implementing Domain-Driven Design](http://www.informit.com/articles/article.aspx?p=2023702). Here Vernon comes to say what VoiceOfUnreasonn is answering. – Laiv Aug 24 '17 at 07:31
  • One question: where/how should the domain event be stored? It has to be stored somewhere as part of the same transaction (otherwise, you could lose some events) in order to be processed later. But then, persisting an event could be considered as persisting a second aggregate root which violates the 1! aggregate/transaction rule? What am I missing? – Xavier Dury Jul 03 '18 at 10:40
  • However, why should a technical requirement related to an infrastructure concern influence the decision taken in the domain ? – Srivathsa Harish Venkataramana Sep 13 '19 at 22:52
  • By definition a storage constraint is not a domain constraint. These are orthogonal concerns. Our domain is a *logical* construct that exists in-memory. What happens when we invoke "Save Domain" is of no concern to the domain itself, and the number of *database* transactions chosen to persist the domain is subject to a very different set of considerations. – user3347715 Jul 13 '20 at 15:51
15

From everything I've read, here and elsewhere, the reason seems to be that changing multiple aggregates in one transaction creates the requirement that they are stored on the same database host. (Let's not even consider two-phase commit.)

This introduces a trade-off, one that makes this a consideration rather than a rule.

Is your bounded context in question ever going to be divided among multiple database hosts? Not multiple databases - that is no problem. Database hosts.

Unless you process incredible volumes or you are bad at choosing table indexes, most contexts are only ever going to use a single database host. This lets you have database transactions around multiple aggregate changes (within one bounded context). This makes consistency guarantees so much easier.

All things considered, I prefer the decision making process like this:

  1. Is it enough for the aggregates to be eventually consistent? If so, use eventual consistency.
  2. Can we reasonably expect the aggregates to always live on a single database host? If so, allow them to share the same database transaction. (Note that if the aggregates are in different bounded contexts, the answer here is probably 'no'.)
  3. We need multiple hosts and guaranteed consistency. Only now do we have to jump through hoops, because our requirements are heavy. Solve the problem through design.

To give an example of #3:

  • The Balance context tracks the balance of each tenant.
  • A tenant's balance must not be negative.
  • The Payment context wants to spend some of a tenant's balance. The deduction must be immediately consistent (to guarantee the previous rule). Should the payment fail, then the balance must eventually go back up.
  • The Balance context exposes in its API a method that returns a new Reservation, reducing the balance by a requested amount, or returning a failure if that amount is not available.
  • The Balance context consumes events from the Payment context.
  • Certain events increase a tenant's balance.
  • Other events pertain to a decrease in the tenant's balance, and are always linked to a prior Reservation. They confirm that Reservation.
  • Reservations are valid for a short time, e.g. 5 minutes. Reservations not confirmed within that time are reversed, increasing the balance to compensate.

Note that this example requires the guarantee that every event is handled exactly once. Particularly, no event must ever be skipped! That is doable, but it is challenging to get right. Most tools are not airtight in this regard. The most easily identified point of failure is if the application server crashes after updating its database, but before publishing its event(s). Guaranteeing exactly-once delivery of events is a worthwhile discussion in its own right.

Timo
  • 372
  • 2
  • 11
  • 1
    `...creates the requirement that they are stored on the same database host`. This is the reason since each microservice needs to have it's own database host. I came to this realisation later on. I'll mark this as accepted as this is the correct answer. – nicholaswmin Aug 29 '18 at 15:26
  • @NikKyriakides I disagree with "each microservice needs to have [its] own database host". One context may contain multiple applications that are separate microservices (e.g. an API and a job service). Being in the same context, these could certainly access the same _database_ (so by definition the same host). And even with separate contexts, we just should have separate _databases_. If the company chooses to host these on the same server, that is fine. I hope this is clear. – Timo Mar 19 '19 at 10:14