In this article, we are going to design an internal architecture of non-trivial microservice which is responsible for both managing its data state and exposing it.
Basically the microservice is responsible for creation and modification and also expose API that allows other services to query data.
CQRS
CQRS, stands for Command Query Responsibility Segregation, is an architectural pattern which separates reading and writing into two different sides.
- Write side: a part that modifies the application state by executing commands.
- Read side: a part that reads the application state by executing queries.
CQRS is derived from Command Query Separation (CQS) principle devised by Bertrand Mayer — author of Eiffel.
Basically, CQS principle is that there can be only two kind of methods on a class: the ones that mutate state and return void and the ones that return state but do not change it.
By applying CQRS, every method should be either a command (perform an action) or a query (return data).
For example, we have an OrderService to order some products
- Without CQRS
- With CQRS
Command
A command is an imperative message to request system to have some tasks performed. There names always use the indicative tense, like PlaceOrder. There is no content in the response, only queries are in charge of getting data.
Query
A query is a READ operation. It can be executed multiple times and will not affect the state of the system. The naming convention of the query is the same as the command’s
Event
An event is a message that serves as a notification for something that has already happened. Like a command, an event must respect a rule regarding names, it is always named with a past-participle verb, such as OrderPlaced
CQRS Pros and Cons
1. Pros:
Independent scaling: allows the read and write workloads to scale independently.
Security: easier to ensure that only the right domain entities are performing writes on the data.
Simpler queries: by storing a materialized view in the read database, the application can avoid complex joins when querying
Optimized data schema: the read side can use a schema that is optimized for queries, while the write side uses a schema that is optimized for updates.
2. Cons:
Complexity: include the Event Sourcing pattern will make it more complex
Messaging: it’s common to use messaging to process commands and publish update events. In that case, the application must handle message failures or duplicate messages.
How to connect the commands and the queries?
Mediator pattern is the answer. One of the implementation of Mediator in .Net Core is MediatR.
Let’s rock!
1. Install libraries by Package Manager Console or find it in Nuget
Install-Package MediatR
Install-Package MediatR.Extensions.Microsoft.DependencyInjection
2. Register MediatR in DI container in Startup.cs file, ConfigureServices method
services.AddMediatR(typeof(Startup));
3. Requests
Requests describe your commands and queries behavior.
Query
Depend on each query, we can add properties (query parameters) into it
Command
DTO
All requests should implement IRequest interface. This interface is a contract which will be used by handler to process request.
4. Handlers
When request is created, you need a handler. The handler implements IRequestHandler interface with definition of input and output types
Each command or query has its own handler, and we will use Repository pattern including Unit of work
Services shouldn’t trust the input from clients (empty data, invalid value, etc…), we need to validate it. One of the library that support us is Fluent validation. You can reference more here.
5. How to use command, query and handlers?
Event Sourcing
Event Sourcing is an approach for maintaining the state of business entities by recording each change of state as an event. It can be used without the CQRS pattern, but if we use CQRS, Event Sourcing fits well in conceptual terms. We cannot delete or modify events, you can only add more.
In Event Sourcing, every time something interesting happens, it is captured and stored as an event. All events are stored in Event Store: a database that supports the concept of Event Sourcing.
The example of events in an e-Commerce system are OrderCreated, PaymentDebited, OrderApproved, OrderRejected, …
In Event Sourcing architecture, when you publish one event from one microservice, other microservices can be reactive to those events and publish another set of events.
Why Event Sourcing?
- Event Sourcing provides the sequence of events that led to the current state. Whether it is for audit or to perform machine learning on past data, the full sequence of events provides more insight than only retaining the current state.
- Event store decouples different microservices. When emitting an event, an application does not need to know about other applications interested in the event
Let’s check the implementation
We’re going to use Marten library for event sourcing. You can see my previous post (part 4) about Marten for some basic information and preparation.
We’re going to create some simple event sourcing for Catalog service, to keep track of order
1. Create events
2. Make simple function to test (note that these codes below just for testing to see how event sourcing work)
After running this function, you can see the generated event sourcing tables with these data
The events are stored in the mt_events table, with these columns:
- seg_id: a sequential identifier that acts as the primary key
- id: a Guid value uniquely identifying the event across databases
- stream_id: a foreign key to the event stream that contains the event
- version: a numerical version of the event’s position within its event stream
- data: the actual event data stored as JSONB
- type: a string identifier for the event type that’s derived from the event type name. For example, events of type OrderAdded would be identified as order_added.
- timestamp: a database timestamp written by the database when events are committed.
- tenant_id: identifies the tenancy of the event
- mt_dotnet_type: the full name of the underlying event type, including assembly name
You can get the full source codes in Github