.NET  

How to Implement CQRS with MediatR in .NET Microservices

Introduction

As applications grow, managing complex business logic inside a single service or controller becomes difficult. You may start noticing problems like messy code, hard-to-maintain logic, and performance issues.

This is where CQRS (Command Query Responsibility Segregation) comes into the picture.

CQRS is a design pattern that separates "read" operations from "write" operations. When combined with MediatR in .NET, it helps you build clean, scalable, and maintainable applications, especially in microservices architecture.

In this guide, we will understand CQRS in very simple words and learn how to implement it step by step using MediatR in a .NET application.

What is CQRS?

CQRS stands for Command Query Responsibility Segregation.

It means:

  • Commands → Used to change data (Create, Update, Delete)

  • Queries → Used to read data (Get, Fetch)

In traditional applications, both read and write logic are mixed together. This makes the system complex.

CQRS separates them into two clear responsibilities.

In simple words:
CQRS separates "reading data" and "changing data" into different paths.

Why Use CQRS?

Using CQRS provides several benefits:

  • Cleaner code structure

  • Better separation of concerns

  • Easier to maintain and test

  • Improved performance (read and write can scale separately)

It is especially useful in large applications and microservices.

What is MediatR?

MediatR is a lightweight library in .NET that helps you implement the mediator pattern.

Instead of calling services directly, you send requests to a mediator, and it routes them to the correct handler.

In simple words:
MediatR acts like a middleman that connects your request to the right logic.

How CQRS and MediatR Work Together

  • CQRS defines structure (Command vs Query)

  • MediatR handles communication (who handles what)

So instead of controllers doing everything, they just send commands or queries.

Step-by-Step: Implement CQRS with MediatR

Let’s build a simple example.

Step 1: Create a .NET Project

dotnet new webapi -n CQRSExample
cd CQRSExample

Step 2: Install Required Packages

dotnet add package MediatR
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection

Step 3: Register MediatR in Program.cs

builder.Services.AddMediatR(cfg =>
    cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));

This enables MediatR in your project.

Step 4: Create a Query (Read Operation)

using MediatR;

public record GetUserQuery(int Id) : IRequest<string>;

This represents a request to get user data.

Step 5: Create Query Handler

public class GetUserQueryHandler : IRequestHandler<GetUserQuery, string>
{
    public Task<string> Handle(GetUserQuery request, CancellationToken cancellationToken)
    {
        return Task.FromResult($"User with ID: {request.Id}");
    }
}

This handles the query and returns data.

Step 6: Create a Command (Write Operation)

public record CreateUserCommand(string Name) : IRequest<string>;

This represents a request to create a user.

Step 7: Create Command Handler

public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, string>
{
    public Task<string> Handle(CreateUserCommand request, CancellationToken cancellationToken)
    {
        return Task.FromResult($"User {request.Name} created successfully");
    }
}

This handles data modification.

Step 8: Use MediatR in Controller

[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
    private readonly IMediator _mediator;

    public UserController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpGet]
    public async Task<IActionResult> Get(int id)
    {
        var result = await _mediator.Send(new GetUserQuery(id));
        return Ok(result);
    }

    [HttpPost]
    public async Task<IActionResult> Create(string name)
    {
        var result = await _mediator.Send(new CreateUserCommand(name));
        return Ok(result);
    }
}

Now your controller is very clean and only sends requests.

How This Improves Your Application

Before CQRS:

  • Controllers had business logic

  • Hard to maintain

After CQRS + MediatR:

  • Logic is separated

  • Easy to test

  • Clean architecture

Difference Between Traditional Approach vs CQRS

FeatureTraditional ApproachCQRS
StructureMixed logicSeparated
MaintainabilityHardEasy
ScalabilityLimitedHigh
TestingDifficultSimple

Best Practices for CQRS in .NET

  • Keep commands and queries separate

  • Use meaningful naming

  • Avoid putting business logic in controllers

  • Use validation for commands

  • Keep handlers small and focused

Real-World Use Cases

  • E-commerce applications

  • Banking systems

  • Large enterprise apps

  • Microservices architecture

Conclusion

CQRS with MediatR is a powerful approach for building clean and scalable .NET applications. It helps you separate responsibilities, reduce complexity, and improve maintainability.

Start with small features and gradually apply CQRS to your application. Over time, you will see how much cleaner and easier your code becomes.