Failed CQRS implement in Microservice architecture.
CQRS is a useful pattern when having a complex business application where reading and writing have separate requirements. For an example, Write wants to maintain a model in normalised form in a RDB but Read can represent model as document in a Document database. But it is not easy to get head around about CQRS. It comes with Reads, Writes, Events, Commands, DDD, Event Sourcing, Eventual Consistency. The common way of implementing CQRS is by creating two services communicating with Events.
Our CQRS Implementation
In order to bring CQRS into our custom framework we used Axon framework. At time when we evaluated CQRS frameworks, Axon was the easiest way to work with and had good support for Spring Boot framework. This is how Axon was used.
We ended up creating two separate services for Writes and Reads. These two services connected via RabbitMQ.
Write Service
Write service handles all the Updates. We did not try to convince developers that write request is the real Command. Controller is responsible to create Command from request and publish Command via CommandGateway. Thecommunication between Controller and Command Handler was Asynchronous. The Command Handler handles the command by loading Aggregates, performing business logics and then publishing Events to RabbitMQ. Same time Events are saved into the Event Store. In this implementation the Event Store acts as our Single Source of Truth.
Read Service
Read service handles all the read requests. No business logic is applied here. Data is updated by listening to Events published to RabbitMQ. Developers had the option to select any Relational Database or MongoDB as read database. What we wanted developers to implement, is a read optimised model. They can store data without doing any normalization to improve read performance.
As you can see, this is a very simple implementation. We separated reads and writes into two services. But why did we fail?
Why We Failed?
Asynchronousy
When we introduce CQRS, the main complain from our developers was “How we are going to update UI, We don’t know whether record is successfully saved or not”. That is a very valid question and what we are familiar with Synchronouscommunication. That is OK and how we use to code from the beginning. Because it is easy to reason about. You can get the complete flow of the request.
So then why we use Asynchronous call in Event Driven Architecture(EDA)? It is simply to improve responsiveness. EDA has nothing to do with Asynchronous communication. We can implement CQRS without using Asynchronous communication.
We can implement CQRS without using Asynchronous communication.
Too much Work
Second main complain was “It take twice much of work to complete a CQRS based microservice.” That is true. Developers have to create two services instead of one, Command service and Read service. But this is how we have implemented CQRS. If we can find easy way to create these, then we can reduce the time that it takes to complete CQRS service. We started to create a Maven archetype that generates skeleton for CQRS based microservices to reduce developers effort.
Apply CQRS for All the services
What we told developers is to use CQRS in every microservice. We thought that entire system should be a CQRS system. At that time that was our understanding. Result of that was unmanageable complexity. This is one of the main mistake we did in implementing CQRS. Greg Young said in one of his video (as I remember) that we have to find areas where we can get a benefit from using CQRS. It can be a small part of the system. If we can’t get a business value then there is no point of apply really amazing concept. CQRS must be a business driven requirement.
Smaller Microservices
We can’t say how small should be a Microservice but we can identify it boundaries. We can use concepts like DDD and Bounded Context to identify microserivce boundaries. In CQRS we use concept called Aggregate. Aggregate is alogically related domain objects. For an example Order and Order Items. We treat Order and Order Items as one single thing. When we save Order we save it with Order Items. We don’t save them separately. When we failed to identify Aggregate we will end up splitting it in to individual microservices. This add huge complexity to CQRS. When you get a request to save Order, how are you going to save Order items?? This same thing happened to us.
Smaller microservice means more microservices. Because of we applied CQRS to all the services we ended up doubling the number of services. It was unmanageable. We took the action to correct both of these issues.
Main reason for this failure was wrong implementation and most of them are not related to CQRS. What I learned from this is that we must understand key concepts of both Microservice and CQRS, only then we can merge these.
Comments
Post a Comment