Backend Security

How to guarantee username uniqueness with CQRS/ES?

To be honest, I thought that my previous post would be the last in the CQRS/ES series, but I forgot to discuss one more thing related to that topic. Many developers don’t know how we should handle the following scenario in our systems:

 

„During the creation of user’s new account I would like to verify rather a username is unique in the whole database. Should I use Event Store or Read Database for a query? Where should I check that?”

 

Seriously, that question is one of the most popular topics connected with CQRS on StackOverflow. Therefore in this article, we’ll try to find out the best solution for this problem.

 

Solution 1. Use Event Store for validation in a Command Handler

Okay so let’s say that we’d use an Event Store for username validation inside a Command Handler. But we have a problem quite similar to that from the sixth part of the series. As we’ve already established using Event Store for getting many domain objects at the same time is inefficient. Why? Imagine that each of your domain objects consists of 100 events and we have almost 5000 users register in our web application (not so big number of users). Now, to reconstruct each object (make a projection) we need to get these 100 events. That gives us 100 * 5000 = 0,5 million events. It’s ridiculous, no doubt about that. So, we need to find another solution.

 

Solution 2. Use Read database for validation in a Command Handler

Since the Event Store’s data structure is not comfortable for this task maybe, we should use a „Current State (Read)” database inside our Command Handler? The query using Entity Framework would be very easy:

 


var isAlreadyTaken = readDb.Users.Any(u => u.UserName == command.UserName);

 

I agree that it might look like a good solution, but it’s not because of two reasons. First, we should not use Read DB in Write side. For me, it’s kind of CQRS smell since we wanted to separate each side completely. Now with this dependency, our flow is perturbed. But even though, this solution wouldn’t work every time. Remember that because of two independent databases we have to deal with eventual consistency. What would happen in the following scenario?

 

cqrs-diagram

 

Imagine that some folk created an account with „TestUser” username. His command was sent to the command handler; a proper domain object was created, and UserCreatedEvent was saved inside Event Store. The event needs to be transported via Event Bus to the appropriate handler to synchronize Read DB. But let’s say that in a moment when event waits inside the bus for processing another folk created an account with „TestUser” username. How our system would behave? Using the Read DB we would check username uniqueness, and it would pass! That is because in that time Read DB was not synchronized yet! I hope that you’ve understood that edge-case 🙂 So, as you can see that solution does not guarantee us safety every time. We need to think about something different.

 

Solution 3. Client validation

The next solution that I’d like to present is client validation. The idea is simple. We create an HTTP request from the client to the read side to check whether the username is already taken. If it’s not, we send the complete necessary data to the write side as a command. Otherwise, we can inform the user about validation error. That also sounds good since it does not require any weird dependencies within the write side. But it also has drawbacks. It’s only client-side validation, and it can also fail because of eventual consistency. But we can use that as a part of some „big plan.”

 

Solution 4. Client Validation + Saga pattern

Okay, so why should we use a client validation if it might not work every time? Because the probability of failing is quite low and we’ll also secure our app on the server-side. How? So we know that Event Store has a bad structure for that kind of task, that’s why we need to use a Read DB. But we also know that we cannot use Read DB within the write-side. So here’s the solution. We can add a constraint to a Read DB which will require a UserName uniqueness. So, in that case adding a new user with already taken login would end with an exception. Great, but we have another problem! Creating a user on the Read DB means that we’ve already inserted an event to the Event Store! Therefore, we should delete that event because read-side has thrown an exception. But remember that deleting events is not allowed. We just can’t do that since the idea is to store the entire, complete history of domain’s state. What we could do is to create UserCreationCanceledEvent and add some logic inside our domain object which would not allow reconstructing that. That sound reasonable but we need to inform our domain to create a proper event from the write-side. How? That’s where Saga pattern comes into play. Consider the following sequence diagram:

 

sq-diagram

 

I know that diagram might look complicated, but it’s not 😉 What we should understand is the Saga’s responsibility. If user’s creation fails, we inform saga about that event. Next, a corrective command is sent to Command Bus to create mentioned UserCreationCanceledEvent which then is saved into Event Store. That’s it! Now, I know that some of you might think that’s a lot of steps to perform, and that’s true. But remember that we also have a quite good client-side validation which should protect our system in most cases, so after all it’s no big deal.

 

Summary

I hope that this article will help you in future. If you know any other good solution to guarantee username uniqueness feel free to share that in the comment section! Next post will be dedicated to Aurelia framework so if you’re also interested in frontend technologies or you want to get aquired with that follow me on Twitter or Facebook to be up to date with brand new articles!

  • Pingback: dotnetomaniak.pl()

  • You could use the user name as a stream to guarantee uniqueness, which sometimes is good but might hit you badly when versioning/changes to your application are applied.

    Another one could be using a helper short lived stream with the user name as a name + projection appending a regular user stream with a NameGranted event. This liberates you from dealing with a natural key in the user stream.
    In majority of cases, using read db in the command and dealing with duplicates later on if they occur (high load/contention system) is the simplest (not the pures) and just works.

  • Pingback: How to guarantee username uniqueness with CQRS/ES? - Forever F[r]ame - How to Code .NET()

  • How about a TryCreateUserEvent, which may be interpreted as failed by Read DB when the uniqueness constraint fails? So on fail the Read DB just ignores this event without throwing the exception.

    • To be honest I don’t get the idea. Throwing an exception by your Read DB is not a problem. What we need to care about is to correct data inside Event Store after inserting an UserCreatedEvent. That is because we insert event into ES first and then we need to synchronize Read DB. The thing is that inserting an event into ES should always succeed since it’s kinda hard to create any reasonable constraint, but Read DB works like typical „current state” DB which might have a specific validation.

      • I suggest to change the events interpretation algorithm so that the CreateUser event will not add a new user to the current snapshot unless all the constraint requirements are met. This way we don’t need a canceling (or correcting) event and we avoid the inconsistent state of the Read DB in-between the user creating event and the canceling one.
        And to make it more obvious to the developer I also suggest to rename the event to TryCreateUser.

    • Daniel Vásquez

      I agree with this concept. What is happening, is a process (Register an user) which needs to coordinate accross several aggregates (in this case, all the already registered user names).
      So, the first command would need to be a request (which would never fail due logical reasons) instead of an order to register. The „UserRegistrationRequested” event would start a saga who could request to a „check names service” in a ad hoc names repository if this names exists already, writing a line against a db with a unique constraint for names. If the write succeeds, this would mean that is the first request that arrived to this point so it would be a kind of lock for that name. Having the answer of this service (all this implemented as an asynchronous messages exchange), saga would issue a RegisterUser (now it’s time) command.

  • Роман Максимович

    Why i can’t use username as User AR id?

    • Arek Bal

      Of course you could, if you are able to guarantee uniqueness through out (for instance by forcing generated usernames on users). Emails could be good for this as well (if you are willing to rely on them) but only in case you deny access until verified.

  • TuanFoo Hong

    How should client get notify about fail to create user event? With assumption of ui was block by waiting for result whether fail ot success?

  • I would make the user management system to be an external dependency of the CQRS-based system, with a separate consistency mechanism (immediate rather than eventual).

  • Pingback: muse headband()

  • Pingback: make money with a iphone()

  • Pingback: Diyala coehuman iraq()

  • Pingback: Aws Colarts Diyala3()

  • Pingback: empresa informatica()

  • Pingback: Aws_Alkhazraji()

  • Pingback: iraqi Seo()

  • Pingback: lowongan kerja 2018()

  • Pingback: pharmacokinetic study of drug()

  • Pingback: empresas manutenção informática()

  • Pingback: http://guaranteedppc.com()

  • Pingback: http://guaranteedppc.com()

  • Pingback: UK Chat()

  • Pingback: cmovieshd()

  • Pingback: warehouses for sale()

  • Alexey Kramarenko

    Thank you for your articles about CQRS & ES. They’ve helped me a lot. I have one question about combining CQRS and microservice architecture. Do I need to create one separate microservice for Command part and second separate microservice for Query part (two Web API projects)? And create communication between them through RabbitMq and Saga. Or both Command & Query part must be placed in one microservice?

  • Pingback: 검증사이트()

  • Pingback: http://www.datingnzcougar.info/()

  • Pingback: read completely()