Reputation: 4984
When using EventSourcing and CQRS you usually have eventual consistency in your read models. Let's say we have a Customer
entity which is eventsourced and this entity has a social security number property. This number must be unique, so when creating a new Customer
we have to check if that number is not already used.
What is the usual approach to do that sort of validation? I can see three options:
Load the whole bunch of events for all Customer
entities, rehydrate all of them, then verify is none is using the number again.
Query the read model, but everything I read about ES+CQRS explicitly says we should not do that because of eventual consistency.
Have a separate table for those numbers only (preferably in the same database of the events, so we can save the events and the numbers in the same transaction, assuming it is a relational database).
Option 3 seems to be the best option if we're using relational databases, because if everything happens within the same transaction, we don't suffer from eventual consistency.
I can see the same happening for queries like 'get all customers with age above 30'. I cannot see different options than rebuilding the whole streams to query them, or querying the read models.
Any thoughts on that?
Upvotes: 1
Views: 136
Reputation: 2623
I discuss almost this exact example in my class.
It is not a "just do this" situation there are many options.
For the vast majority of cases you will not run into conflicts just because the time is so short (seconds at most). As such just detect that it has occurred and tell a human to review things/make a decision. This is quite easy to do.
If you are tracking things and get to a point where automating things would be useful then you basically automate the strategy that the human is using. Note: humans will be making decisions based on different criteria depending on the specific situation.
But for this specific example "This number must be unique, so when creating a new Customer we have to check if that number is not already used." ... use the number in the stream name. This will assure consistency based on the creation of the stream "Customer-382484" ... One client will succeed creating the stream the other will fail.
Upvotes: 0
Reputation: 20561
Without having a dependence on a relational DB, the purer CQRS/ES approach I can think of would be to have the tracking of the SS# -> customer mapping (since it's likely going to be immutable (changing an SS# is possible but rare enough that you may well be able to defer incorporating that until a lot later)) in an event-sourced entity that only tracks that: the customer entity gets created as a projection of that mapping entity emitting an event associating the SS# to a prospective customer.
For example:
AssociateSocialSecurityWithCustomer("012-34-5678", desiredCustId, name)
=> ssnum-to-customer entitySocialSecurityNumberAssociatedWith(desiredCustId, name)
event is emittedIf there are multiple validations like this, you can resort to a saga which is sending commands to multiple entities (e.g. to handle the case where customer already has an SS#) and rolling back on failures.
Upvotes: 1
Reputation: 2003
I would go with 3) too:
You can have a view that selects from the log for the ClientRegistered
event, take the unique ID (SSN?), and extract it from the event payload (most databases allow JSON querying). May not work well if the payload of the event is encrypted but should work with field-level encryption.
Your RegisterClientHandler
can also verify from a separate table and, if the SSN does not exist, a) write to the IDs table, b) write to the log, and c) compensate a) if the write to the log fails for any reason.
Upvotes: 1