Executando verificação de segurança...
11

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.

Carregando publicação patrocinada...