Introduction
Most people who get started with CQRS have issues with this classical example: "how do we verify username uniqueness when a new user registers?"
While user authentication is something that has been implemented numerous amounts of times before, and one should usually not reinvent the wheel but use an existing software library for this particular case, the problem in itself is rather interesting, and happens quite a lot in a domain.
When a user registers with a unique username
Then that registration should be approved
Given a user registration with a certain username was approved
When a new user registers with the same username
Then that registration should be rejected
In this small article, I will show you a simple and proper way to resolve the issue respecting CQRS/DDD principles.
Is That Really What We Want?
Let us think about another way to solve the issue. I will rewrite the scenario a little bit:
When a user registers with a certain username
Then that registration should be pending
Given a user registration with a certain username was approved
When a new user registers with the same username
Then that registration should be pending
Great, Now We Have A Pending Registration! How Is That Helpful?
Not so quick! I have a rule of thumb: whenever I have to communicate between different ARs (even the ones of the same AR type), I use a saga. What is uniqueness validation when you think about it ? Exactly!! A question to all the other ARs whether an entity with the same properties exists. So let us have a second scenario for the saga:
Given a user registration with a certain username is pending
When the information is processed
Then it will approve the registration with that username
Given a user registration with a certain username has been approved
Given a user registration with the same username is pending
When the information is processed
Then it will reject the registration with that username
Please note that scenarios for sagas are a little different:
Given [something happened]
When [all the sagas have processed the events]
Then [a command should be issued]
Example Code
public class UserRegistrationSaga
{
IIndexStore indexstore;
IRunCommand bus;
public UserRegistrationSaga(IRunCommand bus,IIndexStore indexstore )
{
this.indexstore= indexstore;
this.bus = bus;
}
public void OnUserRegistrationPending(string UserRegistrationId,string Username)
{
if (indexstore.ContainsValue<string>("RegistrationUsername",Username))
{
bus.RunCommand(new RejectUserRegistration { UserRegistrationId = UserRegistrationId,
Reason = "Username "+Username+" is already in use"});
}
else
{
indexstore.Add<string>("RegistrationUsername",Username);
bus.RunCommand(new ApproveUserRegistration {
UserRegistrationId = UserRegistrationId, Username=Username });
}
}
public void OnUserRegistrationApproved(string UserRegistrationId,string Username)
{
if (!indexstore.ContainsValue<string>("RegistrationUsername",Username))
{
indexstore.Add<string>("RegistrationUsername",Username);
}
}
public void OnUserRegistrationRejected(string UserRegistrationId,string Username)
{
if (indexstore.ContainsValue<string>("RegistrationUsername",Username))
{
indexstore.Remove<string>("RegistrationUsername",Username);
}
}
}
Conclusion
There you have it, a simple and elegant solution to a problem everybody struggles with in the beginning.