Introduction to CQRS Pattern in .NET Core


21 December 2018

Command Query Responsibility Segregation (CQRS) is an architectural pattern that separates the logic for reading data from the logic for writing data. It’s an advanced design pattern often used to help scale applications by organizing the data management layer more efficiently. In this post, we’ll introduce the CQRS pattern, explain its core concepts, and walk through how to implement CQRS in a .NET Core application.


What is CQRS?

CQRS is a pattern that divides the application logic into two distinct parts:

  1. Commands: These are operations that modify data (e.g., create, update, delete). A command doesn’t return data other than a confirmation of the action.
  2. Queries: These are operations that retrieve data but do not modify it.

The core idea behind CQRS is to separate the concerns of reading and writing data, allowing for optimized performance and scalability for both operations. This approach is often used in systems that need to handle high loads, where the read and write operations have different performance requirements.

Benefits of CQRS

  • Separation of Concerns: Read and write operations are handled separately, improving maintainability.
  • Optimized Data Models: You can have different data models for reading and writing, which allows optimization for performance (e.g., query models can be denormalized for faster reads).
  • Scalability: CQRS allows for better scaling of the read and write sides of the application independently.
  • Improved Security: It simplifies access control, as different operations (queries vs commands) might have different security requirements.

Implementing CQRS in a .NET Core Application

Let’s dive into how to implement CQRS in a simple .NET Core application. In this example, we’ll use an application that manages Products in an inventory system. We’ll separate the command and query responsibilities.

Step 1: Define Command and Query Models

Start by creating Product models for both the read (Query) and write (Command) sides.

// Command: Product creation
public class CreateProductCommand
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

// Query: Product read
public class ProductQuery
{
    public int ProductId { get; set; }
}

The CreateProductCommand model is used to send data for creation purposes, and the ProductQuery is used to fetch product information.

Step 2: Create Handlers for Commands and Queries

CQRS requires handlers for both commands and queries. These handlers implement the business logic for the operations.

Command Handler:

public class CreateProductCommandHandler
{
    private readonly ApplicationDbContext _context;

    public CreateProductCommandHandler(ApplicationDbContext context)
    {
        _context = context;
    }

    public async Task Handle(CreateProductCommand command)
    {
        var product = new Product
        {
            Name = command.Name,
            Price = command.Price
        };

        _context.Products.Add(product);
        await _context.SaveChangesAsync();
    }
}

The CreateProductCommandHandler takes the command model, processes it, and modifies the data (inserting a new product).

Query Handler:

public class GetProductQueryHandler
{
    private readonly ApplicationDbContext _context;

    public GetProductQueryHandler(ApplicationDbContext context)
    {
        _context = context;
    }

    public async Task<Product> Handle(ProductQuery query)
    {
        return await _context.Products
            .FirstOrDefaultAsync(p => p.ProductId == query.ProductId);
    }
}

The GetProductQueryHandler handles the query logic by fetching the required data.

Step 3: Set Up Controllers to Handle Commands and Queries

Next, we set up Controllers in .NET Core to handle the requests for commands and queries separately.

Product Command Controller:

[ApiController]
[Route("api/products")]
public class ProductCommandController : ControllerBase
{
    private readonly CreateProductCommandHandler _commandHandler;

    public ProductCommandController(CreateProductCommandHandler commandHandler)
    {
        _commandHandler = commandHandler;
    }

    [HttpPost]
    public async Task<IActionResult> Create([FromBody] CreateProductCommand command)
    {
        await _commandHandler.Handle(command);
        return Ok("Product created successfully.");
    }
}

Product Query Controller:

[ApiController]
[Route("api/products")]
public class ProductQueryController : ControllerBase
{
    private readonly GetProductQueryHandler _queryHandler;

    public ProductQueryController(GetProductQueryHandler queryHandler)
    {
        _queryHandler = queryHandler;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> Get(int id)
    {
        var product = await _queryHandler.Handle(new ProductQuery { ProductId = id });
        if (product == null)
            return NotFound();

        return Ok(product);
    }
}

In these controllers, we’re using dependency injection to pass the respective command and query handlers. The ProductCommandController handles the creation of new products, and the ProductQueryController handles fetching product data.


Conclusion

In this post, we’ve introduced the Command Query Responsibility Segregation (CQRS) pattern and implemented it in a simple .NET Core application. By separating commands and queries, we gain several benefits such as better scalability, optimized data models, and clearer separation of concerns. While this was a basic example, you can apply CQRS to more complex systems and introduce further enhancements like event sourcing and asynchronous messaging for more advanced use cases.

CQRS can significantly improve your architecture, especially in applications with complex domains and high read/write operations. In the next post, we’ll dive deeper into handling CQRS with Event Sourcing, which is often used in conjunction with this pattern.


Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *