DEV Community

Captain Iminza
Captain Iminza

Posted on

Mastering CQRS Design Pattern with MediatR in C# .NET

INTRODUCTION
The Command Query Responsibility Segregation (CQRS) pattern separates the concerns of read and write operations in an application by using separate models for handling commands (writing data) and queries (reading data). When implementing CQRS in C#, MediatR is a popular library that facilitates the implementation of the pattern by acting as a mediator between commands, queries, and their respective handlers.

Key Components:

  1. Commands: Represent actions that mutate application state. Examples include creating, updating, or deleting entities.
  2. Queries: Represent read operations that retrieve data from the application state without modifying it.
  3. Handlers: Implement the business logic for commands and queries. Each handler is responsible for executing a specific action.

Setting Up MediatR:
Before diving into implementation, ensure you have MediatR installed in your project via NuGet. This can be done using the Package Manager Console or NuGet Package Manager UI in Visual Studio.
Install-Package MediatR

Example Scenario:
Consider a simple e-commerce application where we need to manage products. We'll implement CQRS to handle creating a new product (command) and retrieving a product by ID (query).

Implementation Steps:

  1. Define your commands and queries as plain C# classes, each implementing the appropriate marker interface.
  2. Create handler classes that implement the corresponding interfaces for handling commands and queries.
  3. Register MediatR in your application's dependency injection container (e.g., ASP.NET Core's IServiceCollection).
  4. Send commands and queries through MediatR to invoke their respective handlers.

Code Example

// Command to create a new product
public class CreateProductCommand : IRequest<int>
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

// Query to get a product by ID
public class GetProductByIdQuery : IRequest<Product>
{
    public int Id { get; set; }
}

// Command handler for creating a product
public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, int>
{
    public Task<int> Handle(CreateProductCommand request, CancellationToken cancellationToken)
    {
        // Logic to create a new product and return its ID
        int newProductId = /* Add logic to create a new product */;
        return Task.FromResult(newProductId);
    }
}

// Query handler for retrieving a product by ID
public class GetProductByIdQueryHandler : IRequestHandler<GetProductByIdQuery, Product>
{
    public Task<Product> Handle(GetProductByIdQuery request, CancellationToken cancellationToken)
    {
        // Logic to retrieve a product by ID and return it
        Product product = /* Add logic to retrieve a product by ID */;
        return Task.FromResult(product);
    }
}

// Define the Product class
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Enter fullscreen mode Exit fullscreen mode

Configure MediatR in your application's startup class:

using MediatR;
using Microsoft.Extensions.DependencyInjection;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMediatR(typeof(Startup));
        // Add other services and dependencies
    }
}

Enter fullscreen mode Exit fullscreen mode

With this setup, you can now send commands and queries through MediatR to their respective handlers:

var mediator = serviceProvider.GetRequiredService<IMediator>();

// Sending a command
int newItemId = await mediator.Send(new CreateItemCommand { Name = "New Item", Price = 19.99m });

// Sending a query
Item item = await mediator.Send(new GetItemByIdQuery { Id = 123 });

Enter fullscreen mode Exit fullscreen mode

Benefits of CQRS with MediatR:

  1. Separation of Concerns: CQRS promotes cleaner code by separating read and write operations into distinct components.
  2. Scalability: With CQRS, it's easier to scale read and write operations independently, optimizing performance.
  3. Testability: Handlers can be unit tested in isolation, leading to more robust test suites.
  4. Flexibility: CQRS enables developers to apply different optimization strategies to read and write paths, enhancing flexibility.

Conclusion:
By leveraging the CQRS design pattern with MediatR in C# .NET applications, developers can build scalable, maintainable systems that efficiently handle complex business logic. By embracing the principles of separation of concerns and leveraging the power of MediatR as a mediator, you can unlock new levels of architectural elegance and flexibility in your software projects. Start incorporating CQRS and MediatR into your next C# .NET application and witness the benefits firsthand. Happy coding!

Top comments (0)