Using The CQRS Pattern In C#

Introduction

 
In today’s article, we will look at the CQRS pattern. This stands for the Command and Query Responsibility Segregation (CQRS) pattern. We will look at what this pattern is. What are the advantages of using this pattern? When it should and should not be used, and finally, we will look at a sample implementation of this pattern in a C# application.

The CQRS Pattern

 
The Command and Query Responsibility Segregation (CQRS) pattern states that we must separate the operations for reading the data from the operations for writing or updating the data. This means that functions for reading and writing data are not kept in the same interface or class. The main advantages of doing this include:
  1. Separate teams can work on these operations
  2. Each can be made to scale according to their own needs. Write operations are mostly used much less than read operations
  3. Each can have their own security as per requirements
  4. Read operations can have a different architecture to support caching, conversions to data transformation objects as required by clients
  5. Write operations can include data validation. lookups etc.
However, do keep in mind that this pattern is better suited to larger applications where the requirements and load levels between read and write operations are different. For a simple and small application, the normal CRUD pattern, often auto-generated from ORM tools, is sufficient.
 

A simple implementation of the CQRS Pattern

 
We will now look at a simple implementation of the CQRS pattern in a C# .NET console application. This application will have two repositories. One for the read operations, although we will only have one operation for demo purposes and another for write operations. These repositories will be called by the respective middle-tier components. As the repositories, there will be one for queries (reads) and another for Commands (writes). This application has been created using the Visual Studio 2019 Community Edition, which is a free version.
 
We create a new console application in .NET Core 3.1, as shown below,
 
Using The CQRS Pattern In C# 
 
Using The CQRS Pattern In C#
 
Using The CQRS Pattern In C#
 
The structure of the completed solution looks like shown below:
 
Using The CQRS Pattern In C#
 
Let's first look at the two repositories (These are in the repositories folder)

The Commands (Write) repository
  1. using ConsoleAppCQRSPattern.Models;  
  2. namespace ConsoleAppCQRSPattern.Repositories {  
  3.     public interface IEmployeeCommandsRepository {  
  4.         void SaveEmployee(Employee employee);  
  5.     }  
  6. }  
  7. using ConsoleApp CQRSPattern.Models;  
  8. namespace ConsoleApp CQRSPattern.Repositories {  
  9.     public class EmployeeCommandsRepository: IEmployeeCommandsRepository {  
  10.         public void SaveEmployee(Employee employee) {  
  11.             // Persist the employee record in a data store  
  12.         }  
  13.     }  
  14. }  
Here we see that only write operations (in this case only one) are included.
 
The Queries (Read) repository
  1. using ConsoleAppCQRSPattern.Models;  
  2. namespace ConsoleAppCQRSPattern.Repositories {  
  3.     public interface IEmployeeQueriesRepository {  
  4.         Employee GetByID(int employeeID);  
  5.     }  
  6. }  
  7. using System;  
  8. using ConsoleAppCQRSPattern.Models;  
  9. namespace ConsoleAppCQRSPattern.Repositories {  
  10.     public class EmployeeQueriesRepository: IEmployeeQueriesRepository {  
  11.         public Employee GetByID(int employeeID) {  
  12.             // Get the employee record from a data store  
  13.             // Below is for demo purposes only  
  14.             return new Employee() {  
  15.                 Id = 100,  
  16.                     FirstName = "John",  
  17.                     LastName = "Smith",  
  18.                     DateOfBirth = new DateTime(2000, 1, 1),  
  19.                     Street = "100 Toronto Street",  
  20.                     City = "Toronto",  
  21.                     PostalCode = "k1k 1k1"  
  22.             };  
  23.         }  
  24.     }  
  25. }  
Here we see that only read operations (in this case only one) are included.
 
Next, we look at the two middle-tier components (These are in the Queries and Commands folders),

Queries (To read data)
  1. using ConsoleAppCQRSPattern.DTOs;  
  2. namespace ConsoleAppCQRSPattern.Queries {  
  3.     public interface IEmployeeQueries {  
  4.         EmployeeDTO FindByID(int employeeID);  
  5.     }  
  6. }  
  7. using System;  
  8. using ConsoleAppCQRSPattern.Repositories;  
  9. using ConsoleAppCQRSPattern.DTOs;  
  10. namespace ConsoleAppCQRSPattern.Queries {  
  11.     public class EmployeeQueries {  
  12.         private readonly IEmployeeQueriesRepository _repository;  
  13.         public EmployeeQueries(IEmployeeQueriesRepository repository) {  
  14.             _repository = repository;  
  15.         }  
  16.         public EmployeeDTO FindByID(int employeeID) {  
  17.             var emp = _repository.GetByID(employeeID);  
  18.             return new EmployeeDTO {  
  19.                 Id = emp.Id,  
  20.                     FullName = emp.FirstName + " " + emp.LastName,  
  21.                     Address = emp.Street + " " + emp.City + " " + emp.PostalCode,  
  22.                     Age = Convert.ToInt32(Math.Abs(((DateTime.Now - emp.DateOfBirth).TotalDays) / 365)) - 1  
  23.             };  
  24.         }  
  25.     }  
  26. }  
Commands (To write data)
  1. using ConsoleAppCQRSPattern.Models;  
  2. namespace ConsoleAppCQRSPattern.Commands {  
  3.     public interface IEmployeeCommands {  
  4.         void SaveEmployeeData(Employee employee);  
  5.     }  
  6. }  
  7. using ConsoleAppCQRSPattern.Models;  
  8. using ConsoleAppCQRSPattern.Repositories;  
  9. namespace ConsoleAppCQRSPattern.Commands {  
  10.     public class EmployeeCommands: IEmployeeCommands {  
  11.         private readonly IEmployeeCommandsRepository _repository;  
  12.         public EmployeeCommands(IEmployeeCommandsRepository repository) {  
  13.             _repository = repository;  
  14.         }  
  15.         public void SaveEmployeeData(Employee employee) {  
  16.             _repository.SaveEmployee(employee);  
  17.         }  
  18.     }  
  19. }  
Here we see that we have separate operation handling components. Two other classes are the main Employee class which mirrors our storage item and the Employee DTO class which is used by the Queries (Read) operations to return data in a shape required by the consuming client.
  1. namespace ConsoleAppCQRSPattern.DTOs {  
  2.     public class EmployeeDTO {  
  3.         public int Id {  
  4.             get;  
  5.             set;  
  6.         }  
  7.         public string FullName {  
  8.             get;  
  9.             set;  
  10.         }  
  11.         public int Age {  
  12.             get;  
  13.             set;  
  14.         }  
  15.         public string Address {  
  16.             get;  
  17.             set;  
  18.         }  
  19.     }  
  20. }  
  21. using System;  
  22. namespace ConsoleAppCQRSPattern.Models {  
  23.     public class Employee {  
  24.         public int Id {  
  25.             get;  
  26.             set;  
  27.         }  
  28.         public string FirstName {  
  29.             get;  
  30.             set;  
  31.         }  
  32.         public string LastName {  
  33.             get;  
  34.             set;  
  35.         }  
  36.         public DateTime DateOfBirth {  
  37.             get;  
  38.             set;  
  39.         }  
  40.         public string Street {  
  41.             get;  
  42.             set;  
  43.         }  
  44.         public string City {  
  45.             get;  
  46.             set;  
  47.         }  
  48.         public string PostalCode {  
  49.             get;  
  50.             set;  
  51.         }  
  52.     }  
  53. }  
Finally, we call these operations from the main Program class. Please note that in this demo application, we create an instance of the repository directly. In a real-world scenario, we would probably use some dependency injection framework to do this.
  1. using ConsoleAppCQRSPattern.Commands;  
  2. using ConsoleAppCQRSPattern.Queries;  
  3. using ConsoleAppCQRSPattern.Repositories;  
  4. using System;  
  5. namespace ConsoleAppCQRSPattern {  
  6.     classProgram {  
  7.         staticvoid Main(string[] args) {  
  8.             // Command the Employee Domain to save data  
  9.             var employeeCommand = new EmployeeCommands(new EmployeeCommandsRepository());  
  10.             employeeCommand.SaveEmployeeData(new Models.Employee {  
  11.                 Id = 200, FirstName = "Jane", LastName = "Smith", Street = "150 Toronto Street", City = "Toronto", PostalCode = "j1j1j1", DateOfBirth = new DateTime(2002, 2, 2)  
  12.             });  
  13.             Console.WriteLine($ "Employee data stored");  
  14.             // Query the Employee Domain to get data  
  15.             var employeeQuery = new EmployeeQueries(new EmployeeQueriesRepository());  
  16.             var employee = employeeQuery.FindByID(100);  
  17.             Console.WriteLine($ "Employee ID:{employee.Id}, Name:{employee.FullName}, Address:{employee.Address}, Age:{employee.Age}");  
  18.             Console.ReadKey();  
  19.         }  
  20.     }  
  21. }  
When we run this application, we see the below output,
 
Using The CQRS Pattern In C#

Summary

 
In this article, we looked at the CQRS (Command and Query Responsibility Segregation) pattern and what are the advantages of using this pattern. The example given here is a simple one, but the main idea was to give an outline of how this pattern is to be implemented. The overall layout might be modified further to meet your specific requirement e.g. the repositories could be combined. However, as mentioned earlier, this pattern is better suited to larger applications where the requirements for read and write operations may vary or be overly complex. For simple CRUD operations, we might not need to implement this pattern. Happy Coding!