CQRS - Command Query Responsibility Segregation
Visão Geral
CQRS (Command Query Responsibility Segregation) é um padrão arquitetural que separa as operações de leitura (Queries) das operações de escrita (Commands) em modelos distintos.
Princípio Fundamental: Um método deve ser um Command (que modifica o estado) ou uma Query (que retorna dados), mas nunca ambos.
Conceitos Principais
Commands (Comandos)
Definição: Operações que modificam o estado do sistema.
Características:
- Representam intenções de ação (CreateUser, UpdateOrder, DeleteProduct)
- Não retornam dados (void ou apenas ID do recurso criado)
- Podem falhar (validações, regras de negócio)
- São processados de forma assíncrona (opcional)
- Disparam Domain Events
Exemplo:
public class CreateUserCommand : IRequest<Guid>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
Queries (Consultas)
Definição: Operações que retornam dados sem modificar o estado.
Características:
- Apenas leem dados
- Sempre retornam informações
- Nunca modificam o estado
- Podem ser otimizadas de forma independente
- Podem usar modelos de leitura diferentes (DTOs)
Exemplo:
public class GetUserByIdQuery : IRequest<UserDto>
{
public Guid Id { get; set; }
}
Arquitetura CQRS
Modelo Tradicional (Sem CQRS)

Modelo CQRS

Benefícios do CQRS
1. Separação de Responsabilidades
- Modelos de Escrita: Focados em validação e regras de negócio
- Modelos de Leitura: Otimizados para performance e simplicidade
2. Escalabilidade Independente
- Escalar leituras e escritas de forma independente
- Read replicas para queries
- Write masters para commands
3. Otimização de Performance
- Queries otimizadas sem preocupação com lógica de negócio
- Índices específicos para leitura
- Cache agressivo no lado de leitura
4. Modelos Simplificados
- Sem necessidade de um modelo "universal"
- DTOs específicos para cada caso de uso
- Redução de complexidade
5. Auditoria e Event Sourcing
- Facilita implementação de Event Sourcing
- Histórico completo de mudanças
- Replay de eventos
Implementação no Santa Mão
Estrutura de Pastas
SantaMao.Core/
├── Application/
│ ├── Commands/
│ │ ├── Users/
│ │ │ ├── CreateUser/
│ │ │ │ ├── CreateUserCommand.cs
│ │ │ │ ├── CreateUserCommandHandler.cs
│ │ │ │ └── CreateUserCommandValidator.cs
│ │ │ └── UpdateUser/
│ │ │ ├── UpdateUserCommand.cs
│ │ │ └── UpdateUserCommandHandler.cs
│ │ └── ...
│ └── Queries/
│ ├── Users/
│ │ ├── GetUserById/
│ │ │ ├── GetUserByIdQuery.cs
│ │ │ ├── GetUserByIdQueryHandler.cs
│ │ │ └── GetUserByIdResponse.cs
│ │ └── SearchUsers/
│ │ ├── SearchUsersQuery.cs
│ │ └── SearchUsersQueryHandler.cs
│ └── ...
Command Handler
public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, Guid>
{
private readonly IUserRepository _repository;
private readonly IEventPublisher _eventPublisher;
public async Task<Guid> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
// 1. Validação (já feita pelo FluentValidation)
// 2. Criar agregado
var user = User.Create(
request.FirstName,
request.LastName,
request.Email,
request.Phone
);
// 3. Persistir
await _repository.AddAsync(user, cancellationToken);
// 4. Publicar eventos
await _eventPublisher.PublishAsync(user.DomainEvents, cancellationToken);
// 5. Retornar ID
return user.Id;
}
}
Query Handler
public class GetUserByIdQueryHandler : IRequestHandler<GetUserByIdQuery, UserDto>
{
private readonly IReadRepository _repository;
private readonly IMapper _mapper;
public async Task<UserDto> Handle(GetUserByIdQuery request, CancellationToken cancellationToken)
{
// 1. Buscar dados otimizados
var user = await _repository.GetByIdAsync(request.Id, cancellationToken);
// 2. Mapear para DTO
return _mapper.Map<UserDto>(user);
}
}
Controller
[ApiController]
[Route("api/users")]
public class UsersController : ControllerBase
{
private readonly IMediator _mediator;
// Command
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] CreateUserCommand command)
{
var userId = await _mediator.Send(command);
return CreatedAtAction(nameof(GetUser), new { id = userId }, userId);
}
// Query
[HttpGet("{id}")]
public async Task<IActionResult> GetUser(Guid id)
{
var query = new GetUserByIdQuery { Id = id };
var user = await _mediator.Send(query);
return Ok(user);
}
}
Padrões e Práticas
1. Nomenclatura
Commands: Verbo no imperativo + substantivo
- CreateUserCommand
- UpdateOrderCommand
- DeleteProductCommand
Queries: Get/Search + substantivo
- GetUserByIdQuery
- SearchUsersQuery
- GetOrderDetailsQuery
2. Validação
Commands: Validação rigorosa com FluentValidation
public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
public CreateUserCommandValidator()
{
RuleFor(x => x.FirstName).NotEmpty().MaximumLength(100);
RuleFor(x => x.Email).EmailAddress();
}
}
Queries: Validação mínima (apenas parâmetros básicos)
3. Retorno
Commands:
- void (para updates/deletes)
- Guid (ID do recurso criado)
- Nunca retornar o objeto completo
Queries:
- DTOs otimizados para cada caso de uso
- Paginação para listas
- Projeções específicas
4. MediatR
Utilizamos MediatR como mediador entre Controllers e Handlers:
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
Quando Usar e Quando NÃO Usar
✅ Usar CQRS Quando:
- Leituras e escritas têm características muito diferentes
- Performance de leitura é crítica
- Modelos de escrita complexos com muitas regras de negócio
- Escalabilidade independente é necessária
- Sistema complexo que se beneficia da separação
❌ NÃO Usar CQRS Quando:
- CRUD simples sem regras de negócio complexas
- Sistema pequeno com poucos usuários
- Performance não é crítica
- Equipe pequena sem experiência no padrão
- Deadline curto e separação adiciona complexidade desnecessária
Relacionamento com Outros Padrões
CQRS trabalha em conjunto com:
- DDD - Commands executam métodos do Domain Model, respeitam invariantes e disparam Domain Events
- EDA - Commands publicam eventos após operações bem-sucedidas
- Outbox Pattern - Garante que eventos de Commands sejam publicados com segurança
Fluxo Integrado:
Command → Handler → Aggregate (DDD) → Domain Event → Outbox → Redpanda (EDA)
Conclusão
CQRS no Santa Mão traz clareza e performance para aplicações complexas através da separação entre leituras e escritas.
Benefícios Principais:
✅ Separação clara entre leituras e escritas
✅ Performance otimizada para cada caso de uso
✅ Código mais limpo e manutenível
✅ Escalabilidade independente
A implementação consistente deste padrão em todo o projeto garante que desenvolvedores possam navegar facilmente pelo código e entender rapidamente o fluxo de dados e lógica de negócio.
Última atualização: Novembro 2025
Maintainers: Equipe de Arquitetura Santa Mão