I’ve been playing with event sourcing and EventStore in my free time and I’ve encountered this issue so I thought it might be best to ask here.
To my knowledge Aggregates are generally the source of truth for the write side of your system, but they’re also difficult to query against. Let’s say I have a business rule that requires a user to have a unique Username; unless that’s a part of the stream’s ID, then it would be problematic to try and locate any aggregates with that Username (and thus validate against the Username being unique).
The other solution would be using a readmodel to accomplish this but I’m unsure if this is best practice to check against readmodels on the write side, especially with them being eventually consistent.
I’m sure this is something that people have encountered so I’d be interested to hear what strategies people use for this.
You can check this https://event-driven.io/en/uniqueness-in-event-sourcing/
In general, the “unique user name” thing comes up so many times, so it’s not hard to find what people think about it.
My view is quite simple:
- Querying the read model database in the command handler before executing the domain operation is fine.I mean if you have the aggregate id in hands, you should use the events, otherwise query whatever you like. Don’t do it Ignore everyone who tells you otherwise.
- Define the read model staleness SLA, some could be less than a second, some might be days. It depends on the use case. Not everything should be “immediate” or “real time”, and everyone’s perception of “real time” is different.
- Understand what happens when the constraint is broken. For example, having two users with the same name, would it kill a person or a kitten? If no, is there a financial consequence? If no, what harm would it do at worst? How can you recover from it? Set up a detection mechanist and reconciliation process.
- If it has to be 100% consistent, use the reservation pattern. Write a record somewhere (stream, RDBMS, Redis, MongoDB) a time-limited reservation for the user name as a key. It should self-cleanup. Do it before executing the domain operation. Check if the reservation exists as the first op in the command handler. This way the worst could happen is the user record won’t be created because you can’t write to the actual stream, and the user won’t be able to retry until the reservation expires. Ensure that the reservation record TTL is higher than the read model staleness SLA.
Appreciate the response, super detailed. Cheers