19

Quoting from Vaughn Vernon: Implementing Domain-Driven Design:

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.

Then he goes on to a suggest to use BASE/Eventual Consistency if there really is such an unvoidable requirement.

Here's my problem. BASE is ACIDs weird out-of-state cousin. It's not something you just say "oh yeah let me just go on and use a BASE consistency model here real quick".

Compared to transactional-consistency, eventual-consistency is an exotic consistency model with esoteric theoretical caveats, some of them active research subjects; it requires significant exposure to both it's theoretical concepts and practical limitations to avoid foot-gunning yourself - a run-of-the-mill engineer is unlikely to have adequate experience with it or possibly even having heard of it. By definition, it's harder to reason about it, since it lacks the consistency-guarantee of ACID.

My point being is that choosing it as a consistency model is going to have repercussions that reach through the entire system and I can't reasonably take such a fundamental architectural decision without knowing exactly what the trade-off is that is being implied here.

What's Vernon suggesting in that paper? What is it exactly that makes updating multiple Aggregate Roots in one request/transaction such a bad practice, that giving up the ACID-guarantees is justified?

nicholaswmin
  • 2,019

3 Answers3

18

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
  • 34,589
  • 2
  • 44
  • 83
17

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
  • 402
2

Further quoting Vaughn Vernon:

Limiting the modification of one aggregate instance per transaction may sound overly strict. However, it is a rule of thumb and should be the goal in most cases. It addresses the very reason to use aggregates.

Since aggregates must be designed with a consistency focus, it implies that the user interface should concentrate each request to execute a single command on just one aggregate instance. If user requests try to accomplish too much, it will force the application to modify multiple instances at once.

Therefore, aggregates are chiefly about consistency boundaries and not driven by a desire to design object graphs. Some real-world invariants will be more complex than this. Even so, typically invariants will be less demanding on our modeling efforts, making it possible to design small aggregates

https://www.dddcommunity.org/wp-content/uploads/files/pdf_articles/Vernon_2011_1.pdf

If you need transactional consistency across two or more aggregate instances then effectively they become a single aggregate by definition.

As Vernon discusses under the heading "Don't Trust Every Use Case", I think this is not only about allowing for different instances to be stored on different DB hosts, as the answer from Timo suggests, but also about supporting processing concurrent requests from different users. The wider the consistency rules enforced at the database/aggregate level are, the bigger the chance there is for conflicts between requests.

bdsl
  • 3,924