Manipulador CQRS (C#) com Lógica de Domínio e Pipelines – Explicado através do vídeo de Derek Comartin
A Segregação de Responsabilidades de Comando e Consulta (CQRS) é um padrão de projeto poderoso em aplicações .NET que ajuda a manter uma separação clara entre operações de leitura e gravação. Essa separação permite melhor escalabilidade, testabilidade e controle sobre a lógica de negócios complexa. No entanto, à medida que sua aplicação cresce, seus manipuladores CQRS em C# podem ficar sobrecarregados com lógica, tornando-os mais difíceis de manter e testar.
Em seu esclarecedor vídeo " Limpando manipuladores CQRS inchados com lógica de domínio e pipelines ", Derek Comartin demonstra como limpar esses manipuladores de comandos CQRS, transferindo a lógica para modelos de domínio e estruturando um pipeline para gerenciar a lógica de comando passo a passo. Neste artigo, exploraremos a abordagem de Derek em detalhes e aprenderemos como criar implementações CQRS mais fáceis de manter em C#.
O problema com manipuladores inchados
Derek começa destacando o cenário comum em muitas aplicações web: você abre um manipulador CQRS em C#, e é uma bagunça. Há validação de dados, autorização, lógica de negócios, transições de estado, publicação de eventos, registro de logs — tudo interligado.
Ele ilustra isso usando um objeto de comando para despachar uma remessa. O responsável pelo tratamento é encarregado de:
-
Acessando o banco de dados para carregar o envio.
-
Verificando se o status do envio está pronto.
-
Atualizar o status (ou seja, atualizar os dados).
-
Salvando as alterações de volta para a camada de acesso a dados.
-
Enviar um e-mail.
- Publicar um evento de domínio.
Tudo isso acontece em um só lugar. Isso viola a intenção do padrão CQRS, cujo objetivo é separar as responsabilidades e melhorar o desempenho e a capacidade de manutenção.
Transferindo a lógica de domínio para o modelo de dados
O primeiro passo de Derek é mover a lógica de validação e transição de estado para o modelo de dados. Ele cria um método Dispatch() dentro da classe Shipment. É aqui que reside agora a lógica de domínio.
Em vez de verificar manualmente o status da remessa no manipulador, a lógica é encapsulada neste método, garantindo a integridade dos dados e um comportamento consistente sempre que o despacho for acionado. Isso é fundamental para implementar uma arquitetura limpa em sua aplicação baseada em CQRS.
Por exemplo, qualquer local que chame shipment.Dispatch() realiza automaticamente todas as validações e transições de estado. Isso está em consonância com o padrão de projeto CQRS, ajudando a manter uma clara separação entre o manipulador e a lógica de domínio.
O valor da centralização da lógica
Derek destaca que esse tipo de mudança não se trata de adicionar abstrações desnecessárias. Em vez disso, trata-se de centralizar a lógica que é usada em diferentes partes do código do seu aplicativo. Se vários manipuladores de comandos precisarem despachar uma remessa, essa lógica personalizada deverá residir em um único local — dentro do modelo de domínio.
Isso torna seu modelo de dados mais robusto e suas implementações em C# do manipulador CQRS mais simples e fáceis de manter.
Apresentando o padrão Pipeline
Para simplificar ainda mais o manipulador de comandos, Derek introduz um padrão de pipeline. Essa estrutura processa um comando como uma sequência de pequenas etapas com um único propósito, cada uma recebendo um objeto de contexto e chamando a próxima etapa.
Isso é conceitualmente semelhante ao middleware do ASP.NET Core , e cada etapa se concentra em uma parte específica do fluxo:
-
Recuperar a remessa (ou seja, ler os dados)
-
Enviá-lo (executar operações de escrita)
-
Publicação de eventos
- Salvando no armazenamento de dados
Essas etapas utilizam um objeto de comando compartilhado que flui por todo o pipeline. Isso cria uma implementação limpa e modular da segregação de responsabilidades entre comandos e consultas.
Exemplo de implementação do pipeline
Em sua implementação de exemplo, Derek estrutura o pipeline com etapas como:
-
Carregamento do envio – obtenção dos dados da camada de acesso a dados usando um repositório.
-
Despachar a remessa – invocar o método Dispatch() para aplicar a lógica de domínio.
-
Adicionando um evento de domínio – anexando um evento "ShipmentDispatched" ao contexto.
-
Publicação de eventos – envio de eventos para notificar sistemas externos.
- Salvar alterações – persistir as atualizações no repositório de dados.
Cada etapa representa uma parte distinta da lógica de comando, aprimorando a validação de dados e mantendo as responsabilidades separadas.
Derek também observa que as notificações por e-mail agora são tratadas separadamente, reagindo ao evento de domínio. Isso está em consonância com os princípios de event sourcing e promove a consistência a longo prazo.
Benefícios de teste e manutenção
Uma das maiores vantagens desse padrão é a possibilidade de teste. Com um grande manipulador de comandos, você pode ter várias dependências (por exemplo, repositórios, serviços de e-mail, registradores de logs). Mas quando você divide o manipulador em etapas de pipeline, cada uma requer apenas algumas dependências.
Essa abordagem modular permite testar etapas individuais facilmente com injeção de dependência, usando fakes ou mocks quando necessário. Por exemplo, se você estiver testando uma etapa que chama Dispatch(), não precisa simular um serviço de e-mail ou um publicador de eventos.
Essa separação de responsabilidades segue o padrão de segregação de responsabilidades do CQRS, tornando seus modelos de leitura e gravação mais claros e focados.
Composabilidade e Reutilização
Outra vantagem da abordagem em pipeline é que ela é componível. Se você usar algo como o padrão Outbox, poderá garantir que os eventos sejam publicados somente após a persistência dos modelos de gravação. Esse nível de controle é essencial em implementações de CQRS, onde a consistência e as garantias de entrega são importantes.
Você também pode compartilhar etapas entre diferentes manipuladores CQRS — por exemplo, uma etapa genérica "Salvar alterações" ou uma etapa "Validar solicitação".
Utilizando ferramentas como a biblioteca MediatR, que oferece suporte ao tratamento de comandos e consultas, você pode até mesmo registrar essas etapas por meio de injeção de dependência em seu aplicativo .NET Core usando serviços IServiceCollection.
Para configurar esse sistema, você pode executar o Install-Package MediatR através do Console do Gerenciador de Pacotes do Visual Studio — uma etapa comum ao implementar CQRS em C#.
As compensações
Derek não se esquiva da complexidade adicional que acompanha essa abordagem. Os pipelines introduzem indireção e, ao analisar uma pilha de chamadas, a sensação pode ser de estar navegando em um labirinto.
No entanto, para lógicas de negócios complexas, essa compensação geralmente vale a pena. Se um manipulador tiver mais de 10 dependências e centenas de linhas de lógica, o CQRS permite que os desenvolvedores estruturem e mantenham esses fluxos de forma mais eficiente.
Considerações finais sobre quando refatorar
Derek conclui lembrando os espectadores de que devem considerar cuidadosamente se a implementação em C# do seu manipulador CQRS está realmente inchada. Nem todo cenário exige um pipeline. Seu objetivo é ilustrar possibilidades, e cabe aos desenvolvedores avaliar sua própria implementação de CQRS e determinar se esses padrões serão úteis.
Ele incentiva os desenvolvedores a analisarem as áreas em seu código onde a separação de responsabilidades ajudaria a manter a consistência, tornar o código mais modular e gerenciar melhor as operações de leitura e gravação — especialmente em aplicações web CQRS.
Conclusão
O vídeo de Derek Comartin oferece um guia prático para limpar manipuladores CQRS usando encapsulamento de lógica de domínio e pipelines. Essa abordagem ajuda a resolver problemas de inchaço de código, promove a integridade dos dados e melhora a capacidade de manutenção, dividindo o código do aplicativo em modelos distintos.
Quer você esteja lidando com dados de funcionários, detalhes de produtos ou um novo comando de usuário, aplicar o padrão CQRS com pipelines e design orientado a domínio tornará sua base de código mais escalável, testável e robusta.
Ao utilizar objetos de transferência de dados, modelos separados e manter uma clara separação entre a lógica de leitura e gravação, sua aplicação .NET ficará melhor estruturada e mais fácil de evoluir ao longo do tempo.


