Arquitetura de Software em C# no Mundo Real: Capítulo 6 – Use Cases, Command Handlers e MediatR: o que separa clareza de confusão
Essa série de artigos nasceu da frustração — e da prática.
Se você já tentou organizar um sistema .NET complexo, sabe que não é fácil: cada time tem uma visão diferente, os tutoriais se contradizem, e os “padrões” muitas vezes atrapalham mais do que ajudam. Entre um GenericRepository<T> e um PaymentServiceHandlerProcessorFactory, fica difícil saber o que realmente importa.
Aqui, a proposta é simples: falar sobre arquitetura real, com um olhar crítico, direto e sem medo de questionar modinhas. Vamos explorar estilos, padrões, anti-padrões e armadilhas comuns que se acumulam ao longo dos anos — e mostrar alternativas que funcionam fora do slide de apresentação.
Essa série é para desenvolvedores que já sujaram as mãos. Que já herdaram legado, quebraram produção, e tentaram explicar DDD em reunião de grooming.
Sem fórmulas mágicas. Sem purismo. Só código, contexto e experiência real.
Índice da Série
Capítulo 1 – Os caminhos que a arquitetura pode tomar
Um panorama direto e sem rodeios sobre Transaction Script, Modelo Anêmico, Modelo Rico e DDD.
Capítulo 2 – O modelo anêmico vai te trair (e como saber quando fugir dele)
Quando usar um modelo anêmico parece mais simples, mas te afunda em bugs e complexidade acidental.
Capítulo 3 – A falsa promessa do “service pattern” genérico
AccountService,UserManager,FooHandler: nomes que escondem baixa coesão e design acoplado.
Capítulo 4 – Entidades ricas: encapsulamento é mais que private set;
Como construir entidades com comportamento real e proteger sua integridade.
Capítulo 5 – A camada de aplicação não é um lixo eletrônico
Application Services devem orquestrar, não conter regras de negócio nem virar dumping ground de dependências.
Capítulo 6 – Use Cases, Command Handlers e MediatR: o que separa clareza de confusão
Quando aplicar CQRS traz benefícios reais — e quando só adiciona complexidade desnecessária.
Capítulo 7 – Repositórios que respeitam o domínio (e evitam vazamento de infra)
Um repositório não é só um wrapper de
DbContext. É um contrato do domínio com a persistência.
Capítulo 8 – Domínio expressivo: Value Objects, Invariantes e Eventos de Domínio
Modelando regras com segurança e intenção. O domínio deve falar por si.
Capítulo 9 – Dividindo para conquistar: a importância dos Bounded Contexts
DDD real exige saber onde termina um modelo e começa outro. E como manter essa separação.
Capítulo 10 – Anti-patterns arquiteturais que todo dev .NET já viu (ou cometeu)
Um olhar honesto sobre os maiores pecados que já cometemos em nome da “arquitetura”.
Capítulo 11 – Mitos e Más Interpretações na Arquitetura .NET
Por que MVC não é arquitetura, repositório não é para toda entidade, padrão não é enfeite, e SOLID não é carta branca pra travar refatoração.
Capítulo 6 – Use Cases, Command Handlers e MediatR: o que separa clareza de confusão
Vamos ser sinceros: CQRS virou buzzword.
Todo mundo fala, pouca gente entende, e a maioria que tenta aplicar já começa pensando em event sourcing, projections, Kafka, e uma avalanche de complexidade que não faz o menor sentido pra um CRUD de contas a pagar.
Mas a ideia central é simples.
Tão simples que, quando bem aplicada, parece que nem tem arquitetura.
A essência do CQRS
CQRS significa: Command Query Responsibility Segregation.
Em português claro: separar quem lê de quem escreve.
É só isso.
- Command: você altera o estado.
Exemplo:CreateUser,TransferFunds,ApprovePayment. - Query: você consulta o estado.
Exemplo:GetUserDetails,ListTransactions.
O benefício? Clareza.
- Comandos têm efeitos colaterais.
- Queries não têm efeitos colaterais.
Com isso, você separa os dois mundos e evita aquela zona cinzenta onde um Get() também faz Update() por “conveniência”.
Mas o que CQRS resolve de verdade?
- Evita efeitos colaterais acidentais em métodos de leitura.
- Deixa claro o que é leitura e o que é escrita.
- Permite otimizar cada lado separadamente:
- Leitura pode usar queries diretas, projections ou Dapper.
- Escrita pode ter regras complexas de negócio e persistência transacional.
- Escala de forma diferente: leitura costuma ser mais frequente e pode ser cacheada com agressividade.
CQRS não exige Event Sourcing
Esse é o maior mito.
Você pode aplicar CQRS com Entity Framework e banco relacional sem mexer na sua infraestrutura.
Não precisa de tópicos Kafka, não precisa versionar eventos, não precisa projetar nada.
Olha um exemplo básico:
// Command
public record CreateTradeCommand(decimal Price, decimal Quantity);
// Command Handler
public class CreateTradeHandler
{
public async Task Handle(CreateTradeCommand command)
{
var trade = new Trade(command.Price, command.Quantity);
_repository.Add(trade);
await _unitOfWork.Commit();
}
}
// Query
public class GetTradeDetailsHandler
{
public async Task<TradeDto> Handle(Guid tradeId)
{
return await _db.Trades
.Where(t => t.Id == tradeId)
.Select(t => new TradeDto(t.Id, t.Price, t.Quantity))
.SingleAsync();
}
}
Repare: usamos o mesmo banco, só separamos as responsabilidades.
Mas não é overkill para sistemas pequenos?
Se você acha que precisa de CQRS completo pra tudo, sim, é overkill.
Mas aplicar a ideia central — comandos não retornam dados; queries não causam efeito colateral — já resolve 80% dos problemas.
O restante você evolui com o tempo.
Você pode usar EF no começo. Quando surgir necessidade real, extrair leitura para Dapper, usar projeções, ou até separar bancos.
CQRS não é tudo ou nada. É uma evolução.
Onde o CQRS pode complicar
- Você exagera nos Command/Query Handlers e cria uma classe pra cada botão do sistema.
- Começa a criar pipelines complexos (cache, log, retry, metrics, etc) para cada handler.
- Usa MediatR com tanto comportamento global que você nem sabe mais onde o código executa.
- Faz a separação de leitura/escrita até no banco (write database vs. read replica) antes de precisar.
CQRS mal aplicado vira arquitetura cerimonial.
O segredo é não aplicar tudo de uma vez. Vá separando quando o acoplamento começar a doer.
MediatR: amigo ou inimigo?
MediatR é um bom facilitador para CQRS. Ele te força a pensar em comandos e queries como classes distintas. Ele permite usar pipelines. Ele ajuda a manter handlers pequenos.
Mas o problema não é o MediatR — é como você usa.
Se você precisa de um diagrama pra entender o fluxo de um comando, você já passou do ponto.
Use MediatR se ele ajudar.
Abandone se estiver escondendo complexidade com “mágica”.
E como tudo isso se conecta?
- Controllers disparam comandos/queries
- Command Handlers implementam os casos de uso orquestrando domínio e infraestrutura
- Entidades encapsulam regras
- Queries Handlers leem direto de onde for melhor (EF, Dapper, Redis…)
- Eventos são publicados no final da transação via outbox, se necessário
Conclusão
CQRS não é um framework. É uma forma de pensar.
Separar leitura de escrita é um passo simples que traz clareza, previsibilidade e testabilidade.
Se aplicado com moderação, CQRS te ajuda a crescer com o sistema, sem amarrar seus pés antes de começar a correr.
Próximo capítulo:
Vamos falar sobre os repositórios — e por que muita gente ainda usa GenericRepository<T> achando que está aplicando DDD, quando na verdade está só criando um DAL com nome bonito.