Back to Home

CQRS: the next step CRUD

June 17, 2026

•
Architecture
CQRS Pattern

CQRS (Command Query Responsibility Segregation) is an architecture pattern that separates data-modifying operations and data-reading operations into two distinct paths.

  • Command → operations that change state/data (Create, Update, Delete).
  • Query → operations that only read data.

Without CQRS

Typically, a single service/repository handles everything:

public class UserService
{
    public User GetById(int id) { ... }
    public void Create(User user) { ... }
    public void Update(User user) { ... }
    public void Delete(int id) { ... }
}

With CQRS

Commands and Queries are separated:

// Command
public record CreateUserCommand(
    string Name,
    string Email
);
 
// Query
public record GetUserByIdQuery(
    int Id
);

Each has its own handler:

public class CreateUserCommandHandler
{
    public async Task Handle(CreateUserCommand command)
    {
        // insert into database
    }
}
 
public class GetUserByIdQueryHandler
{
    public async Task<UserDto> Handle(GetUserByIdQuery query)
    {
        // read data
    }
}

Example in ASP.NET Core + MediatR

Command:

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

Handler:

public class CreateUserCommandHandler
    : IRequestHandler<CreateUserCommand, int>
{
    public async Task<int> Handle(
        CreateUserCommand request,
        CancellationToken cancellationToken)
    {
        // save user
        return user.Id;
    }
}

Query:

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

Handler:

public class GetUserQueryHandler
    : IRequestHandler<GetUserQuery, UserDto>
{
    public async Task<UserDto> Handle(
        GetUserQuery request,
        CancellationToken cancellationToken)
    {
        // read user
    }
}

Controller:

[HttpGet("{id}")]
public async Task<UserDto> Get(int id)
{
    return await _mediator.Send(
        new GetUserQuery(id));
}
 
[HttpPost]
public async Task<int> Create(
    CreateUserCommand command)
{
    return await _mediator.Send(command);
}

Pros

  • Code is more structured.
  • Read and write logic don't get mixed up.
  • Queries can be optimized specifically for reading.
  • Great fit for complex applications.
  • Easy to add pipelines like logging, validation, and authorization.

Cons

  • Increases the number of files and complexity.
  • Overkill for simple CRUD applications.
  • Requires discipline in separating commands and queries.

When to use CQRS?

Use it when:

  • The application is growing in size.
  • Business logic is complex.
  • You have many different queries for various UI needs.
  • The team is large enough that separation of concerns helps.

Avoid it when:

  • It's just a simple CRUD application.
  • It's a small project with minimal business logic.
NextA week with hermes agent