I am working with a domain where invariants were identified on different “levels” so to say. The real domain is too complicated to describe, so I’ll come up with a simple-to-understand analogy. Some of the rules might be painfully artificial, but bear with me. They present the same problems as the one I’m facing.
Let’s say we have a system that manages user accounts for an organization. Users can create and increment counters (because it’s good to be able to count on something). There are some rules, like
- “The user cannot increment the same counter twice in less than 30 seconds”
- “The user must have either a phone number or an e-mail address specified”
- “The user can provide up to five e-mail addresses”
And there are some rules that apply to the users as a group.
4. “No e-mail address can be registered twice” (so one user cannot specify an e-mail address another user has specified, not even for a tiny fraction of a second)
5. “There can only be a single supersystemadministratorgod user at any time” (let’s say users have different levels, that can change along the way via promotions by higher level users, but the highest level can only be granted by a quorum of slightly lower level users, demoting the previous god - again, hard consistency requirement)
Users are expected to obsessively manipulate their counters, but it is a rare occassion that they edit their e-mail addresses.
So the problem is there’s rules I can enforce by loading just a user. At the same time there’s rules that I can enforce by loading data related to a large number of users.
Now in a traditional system I think I would handle this by creating an “Aggregate” for User that’s responsible for enforcing 1-3, and a domain service for 4. and 5.
In a system with DDD and ES, I struggle to understand how that would work.
Now in my understanding if we create an aggregate that cares about the details of a single user, it can enforce 1 - 3 but not 4. and 5. On the other hand if we create an aggregate that has the data of all users, all of the rules can be enforced, but it means having a single instance with way too many events happening to it, and with many users clicking “+” at the same time, would lead to performance issues.
So one way I could think of is to create the aggregate “Users” that spans all users but cares about only the rights and the emails, and one that’s for a single “User”, which has the counters, the e-mails, phone number, etc. Requests to edit the e-mail address list (which should be rare), would have to go to Users, which would need to do the validation and the manage the e-mail address list, while incrementing counters would go to aggregates representing individual users.
When a user account is to be created, the first command would have to hit “Users” and there a new entity would be created for the aggregate, saving an event in the stream of “Users”. A process manager would then pick this event up and create a “User” aggregate with its own stream for the events related to counter manipulations and things that do not affect other users.
What do you think about such an approach? Am I overcomplicating? Is there an easier way?