Reactive Architecture: Event Sourcing and CQRS
In the last two editions of reactive architecture, we have covered the basics of reactive architecture, and the four principles of reactive architecture. We then looked at how these four principles are applied in building scalable systems. We looked at some of the techniques that can be very useful to scale up or down our application. In case you have missed the earlier parts, here are some quick links to get up to date on Reactive Architecture :
According to the Reactive Manifesto, a system must be both elastic and resilient in order to be responsive. By utilizing a message-driven framework, it is able to achieve those objectives. However, how do those messages appear? And how do they fit into the Reactive principles to build systems?
Using events as a tool for creating these systems is a frequent approach. Using a Command Query Responsibility Segregation design, these events can be leveraged to build views of the model (CQRS). This then enables us to build our system in a way that is more elastic, and more resilient.
What is CQRS/ES?
Command Query Responsibility Segregation and Event Sourcing are referred to as CQRS/ES. While each of these two tools can be used separately, they are frequently combined.
Together, they provide us with a set of methods that let us create applications that are more elastic and resilient. They are essentially giving us a method to create more reactive applications.
Managing State in A System
In reactive architecture, there are mainly two techniques that are used to store the state of the system. States are stored in reactive architecture in order for the system to be Reactive, Elastic, Resilient, and Scalable at all times. Two main techniques used for this are:
State-based persistence means that an update replaces the previous state with the new one.
It is a widely used approach that keeps track of the application's current state in a database or other storage medium. Here is an illustration of the idea:
Consider that you are developing a web application that enables users to make and manage their to-do lists. This system updates each user's to-do list as they add or finish items by keeping track of its current state in memory. The state of each user's to-do list has to be persisted in a database so that it is not lost when the server is shut down or restarted.
To do this, you might keep the current status of each to-do list in a relational database like MySQL. To keep details about each job on the to-do list, you might build a table in the database with columns like user id, task id, task description, is completed, and created at.
You would update the state in memory and the related records in the database as the user adds or completes tasks. When the user signs in again or the server restarts, you can obtain the most current state of the to-do list from the database and load it into memory.
This is a straightforward illustration of state-based persistence, where the application's current state is saved in a database and updated on demand.
However, there are certain problems with this method as you can imagine:
1. Domain Requirements: Keeping the current state in State-Based Persistence means capturing where you are and how you got there. This means that it captures the destination and not the journey.
This poses an issue for the developers as the Domain Requirements are fluid, they change from time to time. When you first start out an application, you design it in order to target a certain type of user base, once the application gets deployed, it might start getting traction from a different type of user base, this means adding new code and making new changes, State-Based Persistence model may make it seem like an uphill task.
2. You can’t retroactively add new domain insights: Imagine after a few weeks you start new insights about your business from whatever insight software you are using at that, Now adding configurations and code based on those metrics also becomes an uphill task for developers to integrate with their old users. Let me explain this: When they will add new features or fix bugs, these changes will only be made to the new state and they can only be accessed by the users who have registered on the application before these changes were made by updating their application but as we can never guarantee how long it might take for a user to update their application, it poses a problem.
Other problems associated with State Based Persistence are:
1. Data consistency: Maintaining data consistency is a challenge with state-based persistence, particularly in systems with intricate interactions between various types of data. It may be difficult to ensure that edits to one table do not violate the requirements of another table, for instance, if the application contains many tables with foreign key constraints.
2. Performance: Especially in systems with high write and read rates, getting data from a database might take a long time. Many applications employ caching technologies to save frequently requested data in memory or they use NoSQL databases, which offer faster write and read speeds, to address performance difficulties.
Now, let’s move on to Event Sourcing
State-based persistence has some drawbacks despite being a solid general-purpose method. So what happens if we wish to get around or evade those restrictions? We can then employ an approach like event sourcing.
What is event sourcing?
In reactive systems, the design pattern of event sourcing is frequently applied. Event sourcing stores an application's state as a list of previously occurring events. These unchangeable events serve as a historical record of all alterations to the system's status over time.
Event sourcing can be utilized in a reactive architecture to make sure the system can handle a lot of events in real time. This is so that the system may respond to events as they happen rather than having to wait for a batch of events to be processed. Events are processed asynchronously.
Events are often published to an event bus or message queue in a reactive system that leverages event sourcing, where they are consumed by one or more event handlers. These event handlers are responsible for updating the state of the system based on the events that they receive.
Event SoUrcing is a widely practiced technique in reactive architecture and is often combined with another technique known as CQRS
What is CQRS?
CQRS, which stands for Command Query Responsibility Segregation, is a design pattern that is often used in complex software systems. The basic idea behind CQRS is to separate the read and write operations of an application into separate models, each with its own responsibility.
In traditional architecture, a single model is responsible for both read and write operations. This can lead to complex and tightly coupled code, as well as performance issues, since read and write operations can place different demands on the system.
CQRS separates the read and writes operations by creating two separate models: a Command model that handles write operations (i.e. commands that change the state of the system), and a Query model that handles read operations (i.e. queries that retrieve information from the system). These models can be implemented using different architectures, such as a CRUD model for the Command side and a database optimized for read performance for the Query side.
By separating the read and write operations, CQRS can help to simplify the code and improve performance, since each model can be optimized for its specific responsibilities. It also allows for more flexible scaling of the system, since the read and write operations can be scaled independently based on their respective demands.
One important aspect of CQRS is that it requires a mechanism for keeping the Query model in sync with the Command model. This is typically achieved by using an event-driven architecture, where changes to the Command model are published as events, which are then used to update the Query model. This allows for real-time or near-real-time updates of the Query model, while also providing a reliable and resilient way to manage the state of the system over time.
Overall, CQRS is a powerful design pattern that can be used to build complex software systems that are scalable, maintainable, and reliable.
How do Event Sourcing and CQRS work together?
Event sourcing and CQRS are often used together in complex software systems, as they complement each other well and can provide a powerful set of tools for building scalable and resilient applications.
In a CQRS architecture, the Command model is responsible for processing write operations and updating the state of the system. Instead of updating the state directly, however, the Command model generates events that represent the changes made to the state. These events are then stored in an event log, which serves as a historical record of all changes made to the system over time.
The Query model, on the other hand, is responsible for processing read operations and generating query results. To do this, the Query model reads the events from the event log and uses them to reconstruct the current state of the system. This allows the Query model to provide real-time or near-real-time results to queries, while also providing a reliable and resilient way to manage the state of the system over time.
Event sourcing and CQRS work together to provide a number of benefits. First, event sourcing provides a reliable and resilient way to manage the state of the system over time, since the state is stored as a sequence of events that can be replayed to bring the system back to a previous state. This is especially useful in complex systems where failures can be difficult to diagnose and recover from.
Second, CQRS provides a way to separate the read and write operations of the system, which can help to simplify the code and improve performance. By using different models for read and write operations, each model can be optimized for its specific responsibilities, which can lead to faster and more efficient processing of read and write operations.
Finally, the combination of event sourcing and CQRS provides a powerful set of tools for building scalable and resilient applications. By using event-driven architectures to update the Query model, for example, it is possible to provide real-time or near-real-time results to queries, while also maintaining a reliable and resilient historical record of all changes made to the system over time.
Read V Write models in CQRS?
In CQRS (Command Query Responsibility Segregation), the separation of read and write operations are typically implemented using two distinct models: a Read model and a Write model.
The Write model is responsible for handling commands that change the state of the system. These commands are typically initiated by users or other external systems, and they may involve complex business logic or validation. The Write model processes these commands and generates events that represent the changes made to the state of the system.
The events generated by the Write model are then published to an event log, which serves as a reliable and durable source of truth for all changes made to the system. This event log can be used to reconstruct the state of the system at any point in time, which is a key advantage of event sourcing.
On the other hand, the Read model is responsible for handling queries that retrieve information from the system. The Read model is typically optimized for fast and efficient retrieval of data, and it may use denormalized data structures or other performance optimizations to provide fast responses to queries.
The Read model subscribes to the event log and uses the events to update its own state. By doing this, the Read model can provide real-time or near-real-time responses to queries, even as the state of the system changes over time.
The separation of Read and Write models in CQRS provides several benefits. By separating the models, each model can be optimized for its specific responsibilities, which can lead to faster and more efficient processing of read and write operations. Additionally, by using event sourcing, CQRS provides a reliable and resilient way to manage the state of the system over time, which is especially useful in complex systems where failures can be difficult to diagnose and recover from.
Now, we have seen how Event Sourcing And CQRS work but how they help in building reactive systems
How is Consistency achieved with CQRS?
In CQRS, the Write model processes commands that change the state of the system and generates events that represent these changes. The events are then stored in the event log. The Read model subscribes to the event log and uses the events to update its own state.
Because the Read model is updated asynchronously, it is possible that the Read model may not immediately reflect the most recent changes to the system. However, over time, the Read model will eventually become consistent with the Write model.
This eventual consistency is achieved through the use of mechanisms such as event propagation, caching, and data synchronization. For example, events may be propagated through a message broker or event bus, which ensures that all subscribers to the event log receive the events in a timely manner. Additionally, caching can be used to improve the performance of the Read model by storing frequently accessed data in memory.
By using eventual consistency, CQRS allows for the separation of the Read and Write models, which can lead to faster and more efficient processing of read and write operations. Additionally, by using event sourcing, CQRS provides a reliable and resilient way to manage the state of the system over time, which is especially useful in complex systems where failures can be difficult to diagnose and recover from.
How is Scalability achieved with CQRS?
CQRS provides a lot of useful mechanisms which are helpful in achieving scalability in software systems. Some of the ways in which CQRS helps are as follows:
1. Separation of concerns: By separating the read and write operations into two distinct models, CQRS allows each model to be optimized for its specific responsibilities. This separation of concerns makes it easier to scale the system horizontally by adding more instances of the Read or Write model as needed.
2. Event-driven architecture: In CQRS, the Write model generates events that represent changes made to the state of the system. These events are stored in an event log, and the Read model subscribes to the event log to receive updates. This event-driven architecture allows the Read model to receive updates in near-real-time, which can improve the performance of the system by reducing the need for expensive queries and data processing.
3. Denormalization: The Read model in CQRS is typically optimized for fast and efficient retrieval of data. This is often achieved through denormalization, which involves storing precomputed results in the Read model that can be returned quickly in response to queries. By denormalizing the data, the Read model can provide fast responses to queries without the need for expensive data processing.
4. Caching: Caching is a common technique used in CQRS to improve the performance of the Read model. By caching frequently accessed data in memory, the Read model can respond to queries more quickly and reduce the load on the underlying data store.
5. Partitioning: Partitioning involves dividing the data into smaller, more manageable units that can be processed independently. In CQRS, partitioning can be used to split the data between multiple instances of the Read or Write model, which can help improve scalability by reducing the load on any one instance.
By leveraging these techniques, CQRS can help improve the scalability of a software system, allowing it to handle more users, more data, and more complex workflows.
Real-Life examples of CQRS
Event Sourcing and CQRS play a major role in maintaining states in Reactive Systems. It has been incorporated by a lot of companies in order to make their software have the best user experience possible. Here are a few examples of how these techniques have helped the companies in these fields:
E-commerce platform: An e-commerce platform can use event sourcing and CQRS to handle orders and inventory management. The Write model can receive commands to place orders or update inventory, and it can generate events that represent these changes. The Read model can subscribe to the event log and update its own state in real time. This allows the platform to provide fast and accurate information about product availability and order status to customers.
Financial trading system: A financial trading system can use event sourcing and CQRS to handle trades and market data. The Write model can receive commands to execute trades or update market data, and it can generate events that represent these changes. The Read model can subscribe to the event log and update its own state in real time. This allows the system to provide up-to-date information about market conditions and trade executions to traders.
Healthcare application: A healthcare application can use event sourcing and CQRS to handle patient data and medical records. The Write model can receive commands to update patient records or schedule appointments, and it can generate events that represent these changes. The Read model can subscribe to the event log and update its own state in real time. This allows healthcare providers to access real-time information about patient history, appointments, and treatment plans.
Social media platform: A social media platform can use event sourcing and CQRS to handle user-generated content and social interactions. The Write model can receive commands to create or modify content, and it can generate events that represent these changes. The Read model can subscribe to the event log and update its own state in real-time. This allows the platform to provide real-time updates about user activity, social connections, and trending content.
These are just a few examples of how event sourcing and CQRS can be used in real-world applications. By separating read and write operations, event sourcing and CQRS can improve performance, scalability, and reliability in complex systems. Additionally, the use of an event log as a source of truth can help simplify debugging and auditing of system behavior.