Requests like “How do I ensure the email address in a User aggregate is unique?” or “How do I ensure usernames are only used once on command handling?” reach us quite often. We can rephrase them simply as so: How do I consistently validate consistency among a set in such a system? None of these are Axon specific problems, really, but more so issues you would encounter when using CQRS in combination with Event Sourcing. Due to this, they crop up when using Axon Framework. Before diving in how to tackle this with Axon, let us first explain the problem we are facing here.
Why is this a problem?
So, why is set based consistency validation an issue when you have an application based on CQRS and Event Sourcing? The main concern here is the fact CQRS imposes a separation of your model into two distinct components: the command and the query model. The command model is intended to perform the business validation in the application, whereas the query model is mainly used to provide a response to any queries the application can handle. The shift in prioritization allows both models to be optimized for their intended uses. As such the query model will typically contain the query responses as efficient as possible, or how they will be used. The main focus of the command model on the other hand is to maintain the consistency boundary of the application.
When drafting up the command model, this will mostly be done in forms of aggregates. This concept originates from DDD, describing itself as “a group of associated entities which in regard to data changes act as a single unit”. Additionally, “external references are restricted to a single member of the aggregate, designated as the root”. Furthermore, “a set of consistency rules apply within the aggregate’s boundaries”. To keep an aggregate maintainable and compliant to this set of rules, the group of entities typically never span the entirety of a system. Instead, smaller manageable groups of entities are used. If it would encompass a large chunk of the entire system, there would be no way to guarantee the consistency boundary and be performant at the same time, since the entire aggregate should be locked for every operation.
It is with this we can conceive why set based consistency validation becomes even harder. The aggregate normally encompasses a small portion of the model, whereas a set can be any size. An aggregate can simply only guard its own consistency boundary, disregarding the entirety of the set. So, we need an additional thing to allow for set based validation. A logical first assumption would be to use a query model from our application as the solution to our problem. As CQRS however dictates, the synchronization between both models takes some time, introduce eventual consistency. Especially if events are used to drive both models, we will be held back by this. The query model simply cannot guarantee it knows the entire set due to this and as such it cannot be our solution to the problem.
So, how do we resolve the question: How can I check if an account with a particular email address already exists when doing CQRS? Well, we should look further into our command model. It does not necessarily exist solely from aggregates. A command model can contain any form of data model really. Thus, to solve the set based consistency validation for email addresses for example, we can introduce a small look-up table which contains all the addresses. It is this model which can be consulted during this process to allow for this form of validation. To ensure this additional model deals with the problem correctly, any changes occurring on the model should be immediately consistent with the rest. In Axon there are several ways to make this model consistent and to use it for validation. The rest of this blog will be focused on showing how to update this model and to provide the most reasonable solutions to validate a set's consistency. To check the complete implementations of these you can find them in this repository.
Update the look-up table
A lookup table needs to be created with all existing email addresses. A row in this lookup table should be created on the AccountCreatedEvent and the EmailAddressChangedEvent with the accountId and the email address. You can do this by using an event handling component:
You’ll need to make this processing group subscribing to update the lookup table in the same thread as the event has been applied, by adding this property to your application.properties file:
Subscribing processors will create and maintain a projection immediately after an event has been applied and are immediately consistent. These lookup tables are always owned by the command side only and should not be exposed by using a Query API on top of it.
Now that you have a lookup table filled with all existing email addresses you can actually validate if the email address exists before applying the event.
Validation through a Dispatch Interceptor
You can validate a command even before an Aggregate is loaded or created by using a command dispatch interceptor. A dispatch interceptor intercepts the command message before handling it, allowing us to introduce the required validation. The following interceptor reacts on the CreateAccountCommand specifically, allowing it to validate the email set:
Validation in an External Command Handler
If an account already exists and the user wants to change the email address a RequestEmailChangeCommand can be sent to a command handler outside the Aggregate. This command handler is typically called a “Command Handling Component” or an “External Command Handler”. This command handler will validate the email-set whenever it handles the RequestEmailChangeCommand:
Validation using a Parameter Resolver
Last note worth solution is to introduce a “ParameterResolver”. The ParameterResolver can be build such that it can resolve any type of parameter, including a Boolean value which is true only if the email already exists:
With this resolver in place you can add the boolean emailAlreadyExists to the ChangeEmailAddressCommand
command handler, merging the validation into your command model:
When separating an application in a command and query handling side you must be aware the query handling side is eventually consistent. You cannot base decisions in the command handling side by using the query side, because the query model may not be synchronized yet with the changes made in the command handling side. To resolve this, we can expand upon the command model by introducing an additional table next to the aggregate. To update this additional table consistently in Axon, you can use a subscribing event processor to create and update it. This look-up table should only be used by the command handling side to base its decisions upon when validating commands. To use this table seamlessly within Axon you can for example provide a Command Handler Interceptor, an External Command Handler or a Parameter Resolver.
Many thanks to my colleague Steven van Beelen who helped me writing this blog.
Also check out the podcast 'Exploring Axon' where I talked about this subject (and more) with my colleague Sara Torrey.