Implementing CQRS in ASP.NET (2024)

Implementing CQRS in ASP.NET (3)

I have recently followed a course on Linkedin Learning titled Software Architecture: Patterns for Developers by Peter Morlion. In this course, I learned about the Command Query Responsibility Segregation (CQRS) pattern and I was interested to try it out in ASP.NET Core. In this article, I will walk you through my approach to implementing CQRS in a ASP.NET Core Web API project.

Command Query Responsibility Segregation (CQRS) is a software architectural pattern that separates the responsibility of reading and writing data. In a traditional application, we usually have a single model that is used to read and write data. CQRS introduces two separate models, one for reading data and one for writing data. The model for reading data (read model) is optimized for reading data and the model for writing data (write model) is optimized for writing data.

Let’s define Commands and Queries:

  1. Commands: These operations change the system’s state and may or may not return data.
  2. Queries: These operations retrieve data from the system without modifying its state.

Why is this required? What possible benefits can be achieved by taking the effort to implement this pattern?

One of the main advantages of CQRS is the ability to scale the read and write operations independently. In traditional applications, scaling both operations together is necessary. Having the isolation between read and write models allows more flexibility and allows individual models to be updated without affecting the other. Additionally, implementing CQRS gives the ability to optimize the read model and write model separately for performance.

However, implementing CQRS comes with trade-offs.

One of the main disadvantages is the increased complexity of the system from this pattern. CQRS is not suitable for simple CRUD applications, as it would add unnecessary complexity to the system. It is best suited for complex business domains where the benefits of implementing CQRS outweigh the complexity it adds.

Another concern is maintaining the consistency between the read model and the write model. Additional effort is required to synchronize the data between the two models to ensure consistency. Eventual consistency is a common approach to handle this, where the read model is eventually consistent with the write model (Now read the comic at the top again).

CQRS can also lead to code duplication and increased development time since there is an associated learning curve.

Now let’s dive into the implementation.

First, I built a simple CRUD application for managing contacts. The application has three entities: User, Contact, and Address. A user can have contacts of different types (email, phone, etc.) and addresses in different states.

You would see that a simple CRUD application like this does not have a use case for CQRS. However, we will use this application to demonstrate how CQRS can be implemented.

I created the domain models and a repository which uses an SQLite database to persist the data. Tables for each entity were created in the database.

public class User
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<Contact> Contacts { get; set; }
public List<Address> Addresses { get; set; }
}

public class Contact
{
public string Id { get; set; }
public string Type { get; set; }
public string Detail { get; set; }
public string UserId { get; set; }
}

public class Address
{
public string Id { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Postcode { get; set; }
public string UserId { get; set; }
}

public interface IUserRepository
{
User Get(string userId);
void Create(User user);
void Update(User user);
void Delete(string userId);
}

As you know, I can expose these simple CRUD operations from the repository as a service and use it in the ASP.NET Core controller. And our application would work just fine.

Instead, I need to separate the read and write operations into separate models to implement CQRS pattern in the application.

First I implemented the write side of the application.

I have defined two commands named CreateUserCommand and UpdateUserCommand. These commands are used to create the users and update their contacts and addresses.

public class CreateUserCommand
{
public string FirstName { get; set; }
public string LastName { get; set; }
}

public class UpdateUserCommand
{
public string Id { get; set; }
public List<Contact> Contacts { get; set; }
public List<Address> Addresses { get; set; }
}

To handle the write operations, I created a new repository UserWriteRepository based on the previous UserRepository implementation.

public interface IUserWriteRepository
{
User Get(string userId);
void Create(User user);
void Update(User user);
void Delete(string userId);
Contact GetContact(string contactId);
void CreateContact(Contact contact);
void UpdateContact(Contact contact);
void DeleteContact(string contactId);
Address GetAddress(string addressId);
void CreateAddress(Address address);
void UpdateAddress(Address address);
void DeleteAddress(string addressId);
}

Next I have implemented a service named UserWriteService which uses the UserWriteRepository to handle the write operations.

public interface IUserWriteService
{
User HandleCreateUserCommand(CreateUserCommand command);
User HandleUpdateUserCommand(UpdateUserCommand command);
}

And that completes the write side of the application.

Next we will implement the read side of the application. When it comes to the read operations, note that the data model should be independent of the write model and optimized only for reading data.

We need to define the read model suited to the read operations that we have in the application. In this ASP.NET Core application, end users should be able to get the contact details of a particular user according to the contact type, and the address of a particular user according to the state.

To achieve this, I have defined two queries:

public class ContactByTypeQuery
{
public string UserId { get; set; }
public string ContactType { get; set; }
}

public class AddressByStateQuery
{
public string UserId { get; set; }
public string State { get; set; }
}

I will define two models UserAddress and UserContact to represent the read model.

public class UserAddress
{
public string UserId { get; set; }
public Dictionary<string, AddressByState> AddressByStateDictionary { get; set; }
}

public class UserContact
{
public string UserId { get; set; }
public Dictionary<string, ContactByType> ContactByTypeDictionary { get; set; }
}

And I will create few new database tables for the read model:

CREATE TABLE UserAddresses
(
UserId NVARCHAR(255) NOT NULL,
AddressByStateId NVARCHAR(255) NOT NULL,
FOREIGN KEY(AddressByStateId) REFERENCES AddressByState(Id),
PRIMARY KEY(UserId, AddressByStateId)
);

CREATE TABLE AddressByState
(
Id NVARCHAR(255) PRIMARY KEY,
State NVARCHAR(255) NOT NULL,
City NVARCHAR(255) NOT NULL,
Postcode NVARCHAR(255) NOT NULL
);

CREATE TABLE UserContacts
(
UserId NVARCHAR(255) NOT NULL,
ContactByTypeId NVARCHAR(255) NOT NULL,
FOREIGN KEY(ContactByTypeId) REFERENCES ContactByType(Id),
PRIMARY KEY(UserId, ContactByTypeId)
);

CREATE TABLE ContactByType
(
Id NVARCHAR(255) PRIMARY KEY,
Type NVARCHAR(255) NOT NULL,
Detail NVARCHAR(255) NOT NULL
);

To handle the read operations, we need to define a new repository called UserReadRepository.

public interface IUserReadRepository
{
UserContact GetUserContact(string userId);
UserAddress GetUserAddress(string userId);
}

Next, I have implemented a service named UserReadService which uses the UserReadRepository to handle the read operations.

public interface IUserReadService
{
ContactByType Handle(ContactByTypeQuery query);
AddressByState Handle(AddressByStateQuery query);
}

Now we can read the required data from the read model tables and return the expected results.

But you’ll notice that the read model tables are empty and we didn’t add any data to them.

How do these read model tables get updated when a user is created or updated from the write model? To achieve this, we need to implement a mechanism to synchronize the data between the read and write models.

I have implemented a UserProjector to handle this synchronization.

public interface IUserProjector
{
void Project(User user);
}

Ideally, this synchronization should be done asynchronously to avoid blocking the write operations. But for simplicity, I have called the project method synchronously in the UserWriteService.

public User HandleUpdateUserCommand(UpdateUserCommand command)
{
User user = _userWriteRepository.Get(command.Id);
user.Contacts = UpdateContacts(user, command.Contacts);
user.Addresses = UpdateAddresses(user, command.Addresses);
_userProjector.Project(user);
return user;
}

With the synchronization in place, the read model tables will be updated whenever a user is updated.

And that completes the implementation of the CQRS pattern in the application.

The source code for this application can be found in the repo:

Follow the below steps to run the application:

  1. Clone the repository.
  2. Run the recreate-database.bat file to create the database tables.
  3. Open the ContactBook.sln file in Visual Studio.
  4. Run the application.
  5. Use the Swagger UI to test the API endpoints for creating, updating, and reading user contacts.

In this article, my aim was to demonstrate how to implement the CQRS pattern in an ASP.NET Core application. In my next article, I will try to implement Event Sourcing along with CQRS in this application.

If you have any questions or feedback, please feel free to share.

Thank you for reading!

  1. https://github.com/gregoryyoung/m-r/tree/master/SimpleCQRS
  2. https://www.baeldung.com/cqrs-event-sourcing-java
  3. https://www.confluent.io/learn/cqrs
Implementing CQRS in ASP.NET (2024)

FAQs

How to implement CQRS in ASP.NET Core? ›

Let's explore how to implement CQRS using MediatR in an ASP.NET Core 6 application:
  1. Create a .NET Core 6 Web Application. ...
  2. Install MediatR Package. ...
  3. Define Commands and Queries. ...
  4. Create Command Handlers. ...
  5. Create Query Handlers. ...
  6. Configure MediatR In Program.cs. ...
  7. Use MediatR in Controllers.
Sep 25, 2023

How do you implement the CQRS pattern? ›

You can implement the CQRS pattern by using various combinations of databases, including: Using relational database management system (RDBMS) databases for both the command and the query side. Write operations go to the primary database and read operations can be routed to read replicas.

What is the CQRS method in C#? ›

CQRS (Command Query Responsibility Segregation) Pattern is a design pattern for segregating different responsibility types in a software application. The basic idea behind CQRS is to split an application's operations into two groups: Commands, which are responsible for changing the state of the application.

How to implement server side validation in asp net? ›

In Asp.Net Core, the best way to perform the server-side validation is using Model Validation. Likely, Microsoft provides us with the abstract validation layer as validation attributes which contains validation code.

Is CQRS good for Microservices? ›

Advantages of CQRS Design Pattern in Microservices

Independent Scaling: Command and query services can be scaled independently based on the workload they handle. This allows for better resource allocation and improved performance as each service can be optimized for its specific responsibilities.

Can I use MediatR without CQRS? ›

Would it also be possible to use MediatR without CQRS, just to obtain thin Controllers? Yes, you can use MediatR without CQRS. MediatR does not have a hard relation with CQRS although many people seem to talk about the distinction between queries and commands (in videos and courses) when talking about MediatR.

When should you avoid CQRS? ›

CQRS is particularly not suitable for small applications that do not require a high degree of scalability and that do not have complex domain logic, and for applications that have a direct impact on life or health, CQRS is not or only to a very limited extent suitable.

What is the best database for CQRS? ›

Event Stores
  • EventStoreDB - The stream database built for Event Sourcing.
  • EventStore - An event store using PostgreSQL for persistence written in Elixir.
  • Marten DB - . ...
  • Message DB - PostgreSQL.
  • Pravega - Java implementation (CNCF).
  • SQLStreamStore - An event/stream store library in .

Does CQRS improve performance? ›

Implementing CQRS in your application can maximize its performance, scalability, and security. The flexibility created by migrating to CQRS allows a system to better evolve over time and prevents update commands from causing merge conflicts at the domain level.

What problems does CQRS solve? ›

CQRS (command query responsibility segregation) is a programming design and architectural pattern that treats retrieving data and changing data differently. CQRS uses command handlers to simplify the query process and hide complex, multisystem changes.

What is the difference between CRUD and CQRS? ›

Command query responsibility segregation (CQRS) and create, read, update, and delete (CRUD) are well-known application data management architectures. Both systems are designed for the streamlined management of information. However, they differ in implementation, operations, use cases, benefits, and challenges.

Is CQRS a command or query? ›

Commands vs Queries

In CQRS, the system can be divided into separate data models for both commands and queries. Commands operate on something known as the write model, while queries operate on the read model. It is common to have many different read models, each looking at the data from a different perspective.

Which is better, client-side validation or server side validation? ›

In general, it is recommended to use both client-side and server-side validation for maximum security and usability. Client-side validation can provide instant feedback to the user, while server-side validation can ensure that all data is validated correctly.

How to do client-side validation in ASP.NET C#? ›

For client-side validations, we first need jquery-validation and jquery-validation-unobtrusive library along with our general jquery and bootstrap library. In . NET Core, these should be added in wwwroot in the lib folder. Then we need to add the reference to jquery.

What is the master page in ASP.NET C#? ›

ASP.NET master pages allow you to create a consistent layout for the pages in your application. A single master page defines the look and feel and standard behavior that you want for all of the pages (or a group of pages) in your application.

How to perform CRUD operations in ASP.NET Core? ›

  1. Step 1 - Create the Projects. Open Microsoft Visual Studio, then click on the Project option under the New Submenu of File. ...
  2. Step 2 – Create a Product Table in the Database. ...
  3. Step 3 - Create Entity Data Model (EDM) ...
  4. Step 4 - Scaffold the Product Model.
Jun 20, 2024

How to implement CORS in net Core? ›

  1. Step 1: Install the Microsoft. AspNetCore. Cors NuGet Package. ...
  2. Step 2: Configure CORS in Startup. cs. Open the Startup. ...
  3. Step 3: Apply CORS Middleware in the Configure Method. In the Configure method of Startup. ...
  4. Step 4: Run and Test Your Application. Now that CORS is configured, run your .
Dec 30, 2023

How to implement cookies in ASP.NET Core? ›

In the ASP.NET Core web application, you use HttpContext to access the cookie data. To implement this, you use the HttpContextAccessor class, which extends the functionality by providing the IHttpContextAccessor interface. To utilize this interface, it has to be registered with dependency injection.

How to implement search in ASP.NET Core? ›

Create the project
  1. Start Visual Studio and select Create a new project.
  2. Select ASP.NET Core Web App (Model-View-Controller), and then select Next.
  3. Provide a project name, and then select Next.
  4. On the next page, select . NET 6.0 or . NET 7.0 or . ...
  5. Verify that Do not use top-level statements is unchecked.
  6. Select Create.
Apr 23, 2024

Top Articles
Latest Posts
Article information

Author: Sen. Emmett Berge

Last Updated:

Views: 6596

Rating: 5 / 5 (60 voted)

Reviews: 83% of readers found this page helpful

Author information

Name: Sen. Emmett Berge

Birthday: 1993-06-17

Address: 787 Elvis Divide, Port Brice, OH 24507-6802

Phone: +9779049645255

Job: Senior Healthcare Specialist

Hobby: Cycling, Model building, Kitesurfing, Origami, Lapidary, Dance, Basketball

Introduction: My name is Sen. Emmett Berge, I am a funny, vast, charming, courageous, enthusiastic, jolly, famous person who loves writing and wants to share my knowledge and understanding with you.