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?

24
  • 2
    It sounds like he's talking about a distributed system, where each aggregate root might live in a different location. Aug 22, 2017 at 17:36
  • 2
    The Aggregate is by definition the transactional boundary. Aug 22, 2017 at 18:17
  • 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. Aug 22, 2017 at 20:42
  • 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. Aug 23, 2017 at 2:43
  • 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. Aug 23, 2017 at 5:20

2 Answers 2

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.

2
  • 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. Aug 29, 2018 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, 2019 at 10:14
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.

5
  • 1
    Thanks, I've just finished this video which effectively goes in detail about the points you've made above. Aug 23, 2017 at 2:57
  • 1
    An Interview with Vaughn Vernon on Implementing Domain-Driven Design. Here Vernon comes to say what VoiceOfUnreasonn is answering.
    – Laiv
    Aug 24, 2017 at 7: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? Jul 3, 2018 at 10:40
  • However, why should a technical requirement related to an infrastructure concern influence the decision taken in the domain ? Sep 13, 2019 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. Jul 13, 2020 at 15:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.