Open Source CQRS/ES Framework for cloud-native microservices
We have created a unique architectural framework that allows you quickly and efficiently to develop cloud-native microservices using CQRS, DDD, Event Sourcing. This is an implementation of the relatively new CQRS/ES Serverless approach.
The uniqueness of our design is that it is completely serverless. We, probably, the first in the industry to do this without any relational databases or any other IaaS components. This saves huge infrastructure costs, avoids technical risks, migration, versioning issues, and speeds up the start of the project by half a year on average - and we'll show you how to use it.
In this article, you will learn about the technical aspects of creating our CQRS framework and the complexity of implementation. This experience will be useful for engineers interested in the architecture of modern cloud solutions.
Beginning
The company needed a custom talent management platform that fits perfectly for outsourcing business needs. It was named IntelliGrowth. There are similar frameworks on the market to build it, for example, Axoniq or EventFlow, but they do require significant investment in infrastructure.
We have decided to create a framework that is cloud-native and completely serverless, accelerates the development of enterprise systems, and will be used on MIT-license for customer projects as well. For this purpose, we have developed an accelerator, on the basis of which all the microservice products we need are deployed and can be applied GraphQL, gRPC, REST (to your choice). It designed to work in real-time thanks to SignalR and GraphQL Subscription, with ease of deployment and ready-made ARM templates.
The ready solution must meet the following requirements:
● fast automatic scalability;
● the ability to deploy cloud infrastructures and CI / CD for a new project in minutes;
● processing millions of requests per second;
● possibility of rolling-update with zero-downtime;
● low cost of development and hosting of services.
We have created a framework with the most flexible architecture for microservices in Azure Cloud "Intellias.CQRS.Framework" which can be customized for different domains such as FinTech, Healthcare, Enterprise, and more.
Next, let's talk about the steps and details of creating this solution.
Project initiation
We used the following technologies to create a framework: C#, Azure Functions, Azure Service Bus, Cosmos DB, Azure Search, Azure Storage, GraphQL, Azure DevOps. We chose Azure because it is the fastest-growing cloud vendor and attracts many of our customers. There are no plans to adapt to AWS yet.
The system is based on an event-driven architecture built on a serverless model. CQRS allows you to adapt to asymmetric load when writing and reading traffic is not equal. All communication is asynchronous and occurs through commands and events. To simplify business logic, product development, and support, we use DDD.
The system consists of the following main components:
- Front-end SPA application.
- Gateway — processes read requests and send write requests deep into the system.
- Subdomains — parts of the solution responsible for individual business sectors (analog of microservices). They consist of three parts:
- CommandHandler — redirects the command to one or more handlers and produces the event;
- EventHandler — processes events and sends signals to Gateway;
- ProcessManager — responds to events by sending commands to Subdomain.
4. Shared — consists of cross-cutting features like configuration services, monitoring, messaging.
Let us tell you how the complete data flow occurs, as well as reading the data and sending the error to the user.
The user goes to the single page app (SPA), in our case, it is the IntelliGrowth Angular portal. Consider how it interacts with the site as an example of the "Update Competency Name" action. Our user clicks the appropriate buttons on the site, and his request from the SPA goes to the Gateway.
The gateway accepts all requests and knows to which subdomain send them according to the type of request. Currently, there are four parts, each responsible for a separate, connected group of business processes. Requests from the subdomain queue are handled by Command Handler, where all the magic happens - changes are made to the system. We actively use DDD when writing subdomains. The Command Handler activates the responsible Aggregate Root and delegates the command to it.
Aggregate Root accepts, processes, and saves changes in its state as a changeset. At this level, we have implemented the Event Sourcing pattern, which can always trace the cause and effect of actions (like a git history).
After the change, everything is saved. Command Handler sends one signal that the command has been successfully executed and publishes the Integration Event with subdomain status change information. Next, information about the change from Integration events is transmitted to Event Handlers, who in turns update their data in QueryModels. In this case, the name of the competency changes wherever it is used.
QueryModels have aggregate data but are optimized for reading. After the Event Handlers are completed, they send a signal through the Gateway to the SPA and the changes are displayed on the user's screen.
When there is a request to read data rather than a modification, Gateway "looks" immediately in QueryModels and outputs information to the user.
The system is optimized for reading data and has so many events that users always get almost instant feedback on their requests and actions.
If the user's request fails validation, Command Handler does not produce an Integration Event and only releases an error signal. We have developed a powerful error generation mechanism that allows you to provide detailed information about the origin of the error. For example, the following validation error:
will be sent to the SPA in the following form:
Then the user receives a comprehensive answer and sees what went wrong and how to fix it.
Development stages
The system development took place in several stages:
● System kernel development.
The core of the system consists of basic abstractions for DDD, messaging, and infrastructure services. For example, we have a basic abstraction for AggregateRoot that implements EventSourcing.
We also have basic abstractions for commands and events that we support for up to 3 types:
- Integration event — notifies about Subdomain status changes. It is the basis for CQRS and QueryModels construction;
- State event — notifies about AggregateRoot status changes. It is the basis for EventSourcing;
- Signal — notifies about system change, not saved.
● Development of cloud connectors and libraries for Azure services, such as Service Bus, Storage Queue, Table Storage, etc.
● Deployment and monitoring systems development.
● Development of real-time streaming using GraphQL.
● Development of an automated testing framework.
One of the main aspects of the systems was the high consistency of the data. Optimistic locking for commands, guaranty of events deduplication and sequence, two-phase commits, and choreographic orchestration to validate business processes - these are all foundational components used in its reliable operation.
By following DDD and carefully designing the business domain, we have a natural way of scaling the system. Aggregate Root id's partitioning of all messages and data allows the system to withstand peak load from company employees.
Now the theoretical maximum system capacity is up to 500Tb and the maximum performance of the framework is the processing of ~ 4M transactions per second, limited by cloud serverless. Scaling speeds of up to 1000 instances per minute and completely independent deployment of each microservice. For testing, we developed our own event-driven framework to write full-cycle CQRS functional tests.
Framework in Open Source
Currently, our framework is actively used on several projects of the company. The framework is already available in Open Source. So keep calm and check it out :)
We hope you will build the next product with the help of our framework, and we will be happy to help you with that.
Our great team:
Sergiy Seletsky - Senior Solution Architect, Pavel Rytikov - Principal Software Engineer, Oleh Halay - Senior Software Engineer, Roman Smichyk - Senior Angular Engineer, Dmytro Mazur - Software Engineer, Andrii Sokalskyi - Senior Software Engineer, Olha Skochynska - Senior UX Designer, Anna Radchenko - UX Designer, Roman Berezin - Test Engineer, Yana Shulyuk - Business Analyst, Dmitry Yurchenko - Product Manager. Kostiantyn Pidhornyi - Project Manager.
Senior Consultant at Synechron working for Emirates NBD
4 年nice and much required for me
Software Engineer
4 年Nice writeup. thank you for sharing. I have few queries. in your gateway it is traditional API gateway or Facade like implementation? 2. How do you debug that system? 3. Please correct me if I am wrong, any backend side errors you sent to client using signalR. 4. Sorry I did not understand how you handle transaction in this command model?
Technology Executive
4 年Neil Dobson Balvinder Singh