Of all the articles I've read regarding eventual consistency, they typically describe one of two things.
1. The case of multiple writers (e.g., to the same "key" in a distributed key/value store) whose changes need to be reconciled when concurrent writes are detected. This has nothing to do with Command Query Responsibility Segregation or Event Sourcing per se, but rather concurrency writability in general.
2. The time delay between when data is written and when a consistent read can be performed (i.e., containing the same data). This is typically in a Command Query Reponsibility Segregation and Event Sourcing context.
There is a third use of the term "eventual consistency", and it's the one I would like to learn more about. That is, eventual consistency in regards to cross-aggregate business rules in a Command Query Reponsibility Segregation and Event Sourcing context. That is, detecting them and correcting them.
As an example domain of blood donation, assume that there are the following aggregates. A Donor, representing a person who donates blood, and a BloodDonation, regarding a particular vial of blood that a donor has given. Both aggregates have a UUID as a unique identifier.
Now, consider the following business rules:
Rule 1. Blood can only be donated by donors who are at least 18 years of age.
Rule 2. Blood can only be donated by donors who have signed a health declaration.
Rule 3. Blood can only belong to a single donor.
Of the above three rules, the first two are legal requirements, while the second is a law of nature (that is, any amount of blood must have been produced by a single animal). Note, however, that Donors can be less than 18 (perhaps they could also donate money, etc), so this business rule spans the two aggregates.
In modeling the above business rules, I see three options.
Option 1. Combine the Donor and BloodDonation into a single aggregate.
Option 2. Keep zero or more UUIDs of all the BloodDonations within the Donor aggregate.
Option 3. Keep the UUID of the Donor within the BloodDonation aggregate.
Option 1 decreases scalability. That is, a larger aggregate is more likely to be accessed concurrently. For example, one user could be recording a new blood donation while another is updating the donors postal address. I understand that because these affect different fields (i.e., the internal array of blood donations vs the address property) that the application can re-try commands (taking the latest events into consideration) to reduce the concurrency issues here. Option 1 also means that other aggregates cannot reference the BloodDonation aggregate directly (for example, if a BloodFusion aggregate was introduced), because aggregates should only be able to hold references to the root of another aggregate. On the other hand, option 1 allows all three business rules to be enforced as invariants (that is, they can be enforced transactionally).
Option 2 also allows rules 1 and 2 to be enforced transactionally. That is, if a user is trying to record a blood donation, then the Donor aggregate could refuse to add the UUID of the blood donation to its internal array of BloodDonation UUIDs if either the Donor is less than 18, or if they have not signed the health declaration. On the other hand, it is possible for rule 3 to be violated, because it becomes possible for two Donors to reference the sample BloodDonation. This could be a significant problem. For example, if on the read side you are trying to determine what blood type a particular donation is, then it could give multiple answers (B positive and B negative), one for each Donor. In this case, the violation of rule 3 would need to be detected and resolve somehow (this is the type of "eventual consistency" I am pondering at present - rules that span multiple aggregates). Also, option 2 complicates the business process and makes the events less meaningful. For example, the recording of a new blood donation requires a process manager (created with the StartBloodDonationProcess command and recorded with the BloodDonationProcessStarted event). This process manager needs to create a new blood donation (the DonateBlood command and BloodDonated event), then instruct the donor to reference the blood donation (the RecordBloodDonationDonor command and BloodDonationDonorRecorded event). If the first command fails, the blood donation is "deleted" (for lack of a better word), the second command is not issued, and the BloodDonationProcess enters the "failed" state. If the first command succeeds, but the second fails, again, the blood donation is deleted and the BloodDonationProcess enters the "failed" state. When two Donors reference the same BloodDonation, one of them will need the reference removed. The RemoveBloodDonationFromDonor command and BloodDonationRemovedFromDonor event would be used in this case. These events are less meaningful than the CorrectBloodDonationDonor command and BloodDonationDonorCorrected event used by option three.
Option 3 allows rule 3 to be transactionally consistent, by the simple fact that a BloodDonation contains a single UUID pointing to the Donor from which it was obtained. If a user discovers that they recorded the wrong donor for a particular donation (oh my!), they can issue a CorrectBloodDonationDonor command (producing the BloodDonationDonorCorrected event), which is more meaningful that the command/event names used in option 2 ("RemoveBloodDonationFromDonor" and "BloodDonationRemovedFromDonor"). On the other hand, option 3 means that the first two rules can be violated. That is, user A could be issuing a CorrectDonorBirthDate command at the same time as user B is issuing a RecordBloodSample command. The end result of which is that a blood donation was taken from a donor who is less than 18.
There are other things to consider here as well. For example, in option 2, the natural directionality of the relation is from the donor to the blood donation. If the write side needed to look up the donor for a particular blood donation, it would need to use a domain service (or some other mechanism). Likewise, in option 3, the natural directionality of the relation is from the blood donation to the donor. If the write side needed to look up all the blood donations provided by a particular donor, then again a domain service would be required.
So, given the above scenario:
1. Which of the three business "rules" do you see as invariants, and which do you see as rules which can be violated (and rectified by either a user or a process manager)?
2. What do you think of the above analysis of the problem? Are there other things I should consider when choosing between the three options? Are there any other options?
Appreciate your input, folks.