Set Based Consistency Validation
How do I consistently validate consistency among a set in such a system?
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: 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 into how to tackle this with Axon, let us first explain the problem we face 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 that 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. In contrast, the query model is mainly used to respond to any queries the application can handle. The shift in prioritization allows both models to be optimized for their intended uses. The query model will typically contain the query responses as efficiently 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 the form of aggregates. This concept originates from DDD, describing itself as “a group of associated entities regarding 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 encompassed 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 more complex. The aggregate typically encompasses a small portion of the model, whereas a set can be any size. Therefore, an aggregate can only guard its own consistency boundary, disregarding the entirety of the set. So, we need a different thing to allow for set-based validation. A logical first assumption would be to use a query model from our application to solve our problem. However, as CQRS dictates, the synchronization between both models takes some time and introduces eventual consistency. Mainly if events are used to drive both models, we will be held back by this. The query model 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 the data model. Thus, to solve the set-based consistency validation for email addresses, we can introduce a small look-up table containing all the addresses. It is this model which can be consulted during this process to allow for this form of validation. To ensure this other 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 focus on updating this model and providing 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 built 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 subscribe 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 directly consistent. Therefore, 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 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
The last note worth the solution is to introduce a “ParameterResolver”. The ParameterResolver can build such that it can resolve any parameter, including a Boolean value which is true only if the email already exists:
With this resolver in place, you can add the boolean email that already exists to the ChangeEmailAddressCommand command handler, merging the validation into your command model:
Conclusion
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 on the command handling side by using the query side because the query model may not be synchronized yet with the changes made on the command handling side. We can expand upon the command model by introducing an additional table next to the aggregate to resolve this. To update this extra 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.