7

I am reading Vaughn Vernon's series of articles about effective aggregate design.

On the subject of deciding between transactional vs eventual consistency, it states the following:

Discussing this with Eric Evans revealed a very simple and sound guideline. When examining the use case (or story), ask whether it's the job of the user executing the use case to make the data consistent. If it is, try to make it transactionally consistent, but only by adhering to the other rules of aggregate. If it is another user's job, or the job of the system, allow it to be eventually consistent. That bit of wisdom not only provides a convenient tie breaker, it helps us gain a deeper understanding of our domain. It exposes the real system invariants: the ones that must be kept transactionally consistent. That understanding is much more valuable than defaulting to a technical leaning.

This advice confuses me. Large parts of the software that I've written in my career were useful to the end user because it freed them from the responsibility of keeping data consistent. As a result of this, users expect to observe a consistent state all the time, without delay. To a customer paying for a consistency-achieving software, every inconsistent state observed is a bug.

In my experience, when submitting a request, users would usually rather wait a bit and observe a consistent state than have an immediate answer that is changed later on in order to be consistent.

Inversely, when it is the user's responsibility to achieve consistency, our system would warn him about inconsistencies, but allow him to eventually achieve consistency by executing several smaller commands, instead of requiring the user to do so with one giant command.

Why are Evans and Vernon recommending eventual consistency when it is the system's job to get consistency right and transactional consistency when it's the user's responsibility?

Could you point out example constraints and use-cases where transactional or eventual consistency are considered suitable according to the above rule?

3 Answers 3

6

It seems you are overthinking this. The advice is illustrated in a few different and related use cases.

1. User Submits Proposal: Use case where the user is responsible for ensuring Proposal is consistent. In other words, it is considered the user's responsibility to cause a consistent outcome of data mutation.

(A) User defines a Proposal and submits it to the system's Proposals Context

(B) The system persists the new Proposal transactionally

(C) The user immediately sees that the Proposal has been successfully defined

2. Check for Subscription Plan Overuse: Use case continuing from #1 where there is also eventual consistency. The system is responsible for ensuring that the user's subscription plan does not exceed the number of Proposals allotted.

(D) By way of the latest Proposal submission, the Proposal emits the ProposalSubmitted event, which is persisted transactionally along with the Proposal

(E) ProposalSubmitted is published and subsequently received by a message handler in the User Accounts Context.

(F) The Accounts Context increments the count user Proposals, records the value, and if the Proposal limit is reached, emits AccountLimitReached { type=Proposal }

(G) AccountLimitReached is published and subsequently received by a message handlers in the Proposals Context

(H) The Proposal Account records that the user's limit has been reached

(I) AccountLimitReached is also received by the Notifications Context, and an email/SMS/... is sent to the user to notify of account overuse and actions to take

3. Proposal Definition Encounters Paywall: Use case where Proposal Account limit has been recorded, the user is given the opportunity to upgrade, and does so. This is an occasion where the User causes modification in two different Bounded Contexts. Although there is eventual consistency, the user does not perceive that and has been responsible for multi-aggregate persistence.

(A) The user selects the option to define a new Proposal

(B) The system reports that the user's plan limit has been reached and offers an upgrade option

(C) The user clicks the upgrade option and sees a spinning busy icon

(D) The upgrade is recorded in the User Accounts Context, which emits the event AccountLimitIncreased

(E) The user's original request to define a new Proposal is now honored (without further user gestures)

4. User Submits Proposal with Self Verification for Digital Signature: Use case where user is responsible for making two aggregates transactionally consistent.

(A) User defines a Proposal, selects the electronic verification and signature option, and submits it to the system's Proposals Context

(B) The system verifies the user by their secure user token and requests a new digital signature object

(C) The system persists the Proposal and the new digital signature transactionally

(D) The user immediately sees that the Proposal has been successfully defined and is marked with a digital signature

HTH, Vaughn

P.S. Banks don't make account transfers consistent in a single transaction

2
  • Thanks for your answer. The examples seem to presume some unspecified context (what is a proposal?), but are still useful.
    – blubb
    Jan 19, 2021 at 10:04
  • 1
    Say a proposal is for contractors who are writing proposals for potential clients.
    – Vaughn
    Jan 20, 2021 at 12:36
2

There's a fundamental principle about microservices that cannot be denied. Microservices are independent entities; if you are building a system that is based on microservices, then by definition you cannot have them depend on a central data authority (like a relational database), because your microservices would no longer be independent. Giving each microservice its own independent data store means that you must settle for eventual consistency.

It doesn't have to be that way, of course. You could certainly tie all of your "microservices" to a central data store and get transactional consistency everywhere; the Actor Model does this all the time. I just don't know that purists would call that a microservice architecture.

I think Vernon Vaugn's point is that the User is generally not responsible for achieving consistency; the system is. Consistency is a non-functional requirement; it doesn't take the form "As a User, I want to [perform some action] so that I can [achieve some goal]."

That said, users do expect some interactions (like moving money from one checking account to another) to be transactionally consistent, not eventually consistent. You don't want a bank customer to be asking "where has my money gone," only to have it show up in the target account hours later.

2
  • Thanks, this certainly helps. Followup question: "Settling for eventual consistency" sounds a lot like giving up transactional consistency in order to gain something else instead, I'm assuming mainly unlimited scaling. In my domain I think that scaling is actually much less of a concern than immediate consistency... Is there something else that would motivate the use of eventual consistency over transactional consistency?
    – blubb
    Jun 1, 2018 at 18:29
  • Not that I know of. Jun 1, 2018 at 18:50
1

This advice confuses me.

Not your fault.

I suspect that he's trying to express ideas similar to those of Udi Dahan in When to avoid CQRS; a distinction between collaborative and non-collaborative use cases.

Consider a cheque register; a ledger of entries describing each negotiable instrument drawing against an account. Each entry would typically include the unique identifier for the cheque (the account it draws against is usually implicit -- each register is associated with a single account), a memo field, the amount, and a running balance.

That running balance is derived from the details of the cheque itself, and the previous balance entry in the register (state). There's not typically surprise entries from other's, or congestion from trying record several cheques simultaneously. So it's reasonable to assert an invariant that the running balance should match the accumulated total of the entries.

On the other hand, getting the register balance to match the account balance is more complicated - the account balance is going to depend on other collaborators depositing the cheques, transferring the funds, crediting the account. Thus, the work of trying to reconcile the running total in the register with the account total can (and should) be deferred until later.

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.