Hi everyone,
Use case
We want to event source users because users are a valuable asset (they pay our rents !) and we need to keep track of their changes. Each user will have an unique id, an email and a password (and lots of stuff we don’t really care about in this example).
Business rules on users :
- users can change their email
- you can’t have two users with the same email address
Our plan is to store each user in his own stream user-{{user_id}}. A projection will watch the “user” category and link each event into a “users” stream.
We plan tu use a CQRS design, where a “user-command” service deals with the creation and update command to users and emits events into the corresponding user-{{user_id}} stream.
We need users to figure in an LDAP so we can authenticate them.
My questions are :
- how can we ensure, from an aggregate POV, that no user already uses a particular email address ?
- is the idea to put (hashed) passwords in events as dangerous as it looks ?
Scenario 1 : use events !
My first idea is to delegate the email unicity checking to the LDAP (or at least a query service). For that, we create a “LDAP-sync-service”, a query service who watches the “users” stream for user creation and update attempts.
When user-command service receives a creation or email-update command, he emits a “UserCreationAttempted” or “UserEmailUpdateAttempted” event. Service LDAP-sync-service catches the event, tries to “execute” the change in the LDAP.
- failure : LDAP-sync-service rollbacks LDAP and fires “refuseUserCreation” or “refuseUserUpdate” command to user-command service, who then fires a “userCreationFailed” or “userUpdateFailed” event.
- success : LDAP-sync-service fires “acceptUserCreation” or “acceptUserUpdate” command to user-command service, who then fires a “userCreated” or “userUpdated” event.
pros:
-
CQRS
-
events look like business rules
cons: -
my “write” business rules are separated in two services, which looks weird.
-
user-command service can’t synchronously answer if the user is created/updated to his requester !
Scenario 2 : the command service directly uses LDAP
When user-command service receives a creation or email-update command, he first tries to “execute” the changes in the LDAP :
-
LDAP failure : user-command service fires a “userCreationFailed” or “userUpdateFailed” event
-
LDAP success : user-command service fires a “userCreated” or “userUpdated” event
pros: -
simpler, no query
-
user-command service can synchronously answer if the user is created/updated to his requester !
cons: -
no CQRS as the command-service is coupled to a “database” service
Scenario 3 : use projections ?
I feel like projections can help me there, but I don’t really know how. Maybe we could link user-{{user_id}} events to an email-{{user-email}} stream, but in this case wouldn’t it seem weird to my user-command service to first load an email aggregate, and then my user aggregate ?