Topicos: Conceitos de Engenharia de Software
Publicado em 09/10/2026 por AdminCQRS é um padrão arquitetural que propõe separar as operações responsáveis por modificar os dados daquelas destinadas a consultá-los.
Em aplicações .NET, essa abordagem pode ajudar a organizar casos de uso, simplificar consultas complexas e permitir que operações de leitura e escrita sejam otimizadas de maneiras diferentes.
Entretanto, o CQRS também é frequentemente aplicado em situações nas quais uma arquitetura tradicional de serviços e repositórios seria suficiente.
O resultado pode ser uma aplicação com mais classes, handlers, interfaces e mecanismos de comunicação, sem uma melhoria proporcional de desempenho ou manutenção.
Por isso, antes de implementar CQRS em um projeto ASP.NET Core, é importante compreender o problema que o padrão pretende resolver e os custos envolvidos.
Neste artigo, você vai entender o funcionamento do CQRS, conhecer uma implementação simplificada em C#, identificar os principais benefícios e limitações e descobrir quando essa abordagem realmente faz sentido.
CQRS é a sigla para Command Query Responsibility Segregation, expressão que pode ser traduzida como Separação de Responsabilidades entre Comandos e Consultas.
O princípio fundamental é separar duas categorias de operação:
Commands: representam solicitações que alteram o estado da aplicação.
Queries: representam solicitações que consultam informações sem alterar o estado da aplicação.
Em uma aplicação tradicional, o mesmo modelo pode ser utilizado para executar operações de leitura e escrita.
Isso costuma funcionar bem em sistemas simples.
Entretanto, em domínios mais complexos, a lógica necessária para modificar os dados pode ser muito diferente daquela utilizada para consultá-los.
Uma operação de atualização de pedido, por exemplo, pode exigir validações de negócio, controle de permissões e verificação do estado atual da entidade.
Já uma tela de acompanhamento de pedidos pode precisar apenas de uma projeção contendo o número do pedido, o nome do cliente, o estado atual e a data de criação.
O CQRS permite tratar essas responsabilidades separadamente.
A documentação da Microsoft descreve o padrão como uma forma de separar as operações de leitura e gravação, permitindo que cada lado seja organizado e otimizado conforme suas necessidades.
Imagine uma aplicação de comércio eletrônico desenvolvida com ASP.NET Core.
Ela precisa oferecer duas funcionalidades:
Atualizar o estado de um pedido.
Consultar os pedidos de um cliente.
Na abordagem tradicional, as duas operações podem utilizar o mesmo serviço de aplicação, o mesmo modelo de dados e o mesmo repositório.
Isso não é necessariamente um problema.
Entretanto, conforme as regras de negócio e as consultas crescem, a combinação dessas responsabilidades pode tornar o código difícil de manter.
Com CQRS, podemos separar os fluxos.
Um comando representa uma intenção de modificar o estado do sistema.
Por exemplo:
ChangeOrderStatusCommand
Esse comando pode receber o identificador do pedido e o novo estado solicitado.
O processamento deve verificar se o pedido existe, se a transição de estado é permitida e se o usuário possui autorização para realizar a operação.
Depois das validações, o sistema persiste a alteração.
Uma consulta representa uma solicitação de leitura.
Por exemplo:
GetCustomerOrdersQuery
Ela pode receber o identificador do cliente e retornar uma coleção de DTOs com as informações necessárias para a tela.
A consulta não deve modificar o estado da aplicação.
Além de separar as responsabilidades, essa abordagem permite que o formato dos dados retornados seja diferente do modelo utilizado para persistência.
Essa é uma das características mais úteis do CQRS: o modelo de leitura não precisa reproduzir exatamente a estrutura utilizada para modificar os dados.
Não.
Esse é um dos equívocos mais comuns sobre o padrão.
CQRS não exige que a aplicação utilize dois bancos de dados diferentes.
Existem diferentes níveis de implementação.
Na forma mais simples, a aplicação separa os componentes responsáveis por operações de escrita e leitura, mas utiliza o mesmo banco de dados.
Por exemplo, uma aplicação pode utilizar:
EF Core para operações que envolvem entidades e regras de negócio.
Dapper para consultas específicas que precisam retornar projeções otimizadas.
Os dois mecanismos podem acessar o mesmo SQL Server.
Essa organização permite separar responsabilidades sem introduzir a complexidade de sincronizar bancos de dados distintos.
Também é possível utilizar apenas o EF Core, mantendo os fluxos de leitura e escrita separados na camada de aplicação.
Em uma implementação mais avançada, os modelos utilizados para leitura e escrita são diferentes.
O modelo de escrita pode representar o domínio e suas regras de negócio.
O modelo de leitura pode ser composto por DTOs ou projeções adaptadas às necessidades de cada tela ou relatório.
Os dois modelos podem compartilhar a mesma base de dados.
Essa abordagem permite simplificar consultas sem exigir uma estrutura distribuída.
Em cenários mais complexos, o sistema pode utilizar armazenamentos distintos para cada responsabilidade.
Por exemplo:
Um banco relacional para operações transacionais.
Uma estrutura otimizada para consultas e relatórios.
Projeções atualizadas por eventos ou processos assíncronos.
Essa implementação pode oferecer vantagens de escalabilidade e desempenho, mas exige mecanismos adicionais de sincronização e tratamento de falhas.
Quando os dados são atualizados de forma assíncrona, o modelo de leitura pode apresentar um estado ligeiramente desatualizado.
Esse comportamento é conhecido como consistência eventual.
Portanto, separar os bancos de dados é uma decisão arquitetural adicional, e não um requisito obrigatório do CQRS.
Para entender o conceito, vamos construir um exemplo simplificado usando C# e ASP.NET Core.
A implementação não depende de uma biblioteca específica de CQRS. Os componentes serão criados explicitamente para deixar claras suas responsabilidades.
Considere uma aplicação que precisa consultar pedidos e alterar seus estados.
Uma organização possível seria:
Application
├── Orders
│ ├── Commands
│ │ └── ChangeOrderStatus
│ │ ├── ChangeOrderStatusCommand.cs
│ │ └── ChangeOrderStatusHandler.cs
│ │
│ └── Queries
│ └── GetOrderById
│ ├── GetOrderByIdQuery.cs
│ └── GetOrderByIdHandler.cs
Essa estrutura é apenas uma convenção. O CQRS não exige uma organização específica de pastas ou projetos.
O comando representa a solicitação de alteração:
public sealed record ChangeOrderStatusCommand(
int OrderId,
string NewStatus);
O objeto contém os dados necessários para solicitar a mudança.
É importante distinguir o comando da operação que o executa.
O comando expressa a intenção, enquanto o handler ou serviço responsável pelo processamento executa as validações e a alteração.
O handler pode ser responsável por carregar o pedido, verificar as condições necessárias e persistir a mudança.
Uma implementação simplificada seria:
public sealed class ChangeOrderStatusHandler
{
private readonly AppDbContext _context;
public ChangeOrderStatusHandler(
AppDbContext context)
{
_context = context;
}
public async Task Handle(
ChangeOrderStatusCommand command,
CancellationToken cancellationToken)
{
var order = await _context.Orders
.FirstOrDefaultAsync(
x => x.Id == command.OrderId,
cancellationToken);
if (order is null)
{
throw new InvalidOperationException(
"Pedido não encontrado.");
}
order.ChangeStatus(command.NewStatus);
await _context.SaveChangesAsync(
cancellationToken);
}
}
Nesse exemplo, AppDbContext representa um DbContext configurado com o modelo de persistência, enquanto Order representa uma entidade que disponibiliza a operação ChangeStatus.
A implementação de ChangeStatus deve proteger as regras do domínio, como quais transições são permitidas.
Em uma aplicação real, também seria necessário considerar aspectos como autorização, validação de entrada, tratamento de exceções e concorrência.
O exemplo mostra a separação entre o objeto que representa o comando e o componente que executa a operação.
Agora podemos criar uma consulta para recuperar os dados de um pedido:
public sealed record GetOrderByIdQuery(
int OrderId);
O objeto representa uma solicitação de leitura.
O handler pode retornar um DTO em vez de expor diretamente a entidade de domínio.
Por exemplo:
public sealed record OrderDetailsDto(
int Id,
string Status,
decimal Total);
O DTO contém apenas os dados necessários para o consumidor.
Uma implementação simplificada utilizando Entity Framework Core seria:
public sealed class GetOrderByIdHandler
{
private readonly AppDbContext _context;
public GetOrderByIdHandler(
AppDbContext context)
{
_context = context;
}
public async Task Handle(
GetOrderByIdQuery query,
CancellationToken cancellationToken)
{
return await _context.Orders
.AsNoTracking()
.Where(x => x.Id == query.OrderId)
.Select(x => new OrderDetailsDto(
x.Id,
x.Status,
x.Total))
.FirstOrDefaultAsync(cancellationToken);
}
}
Esse exemplo utiliza AsNoTracking() porque a operação é de leitura e não precisa acompanhar as alterações da entidade por meio do contexto do EF Core.
A projeção seleciona apenas os campos necessários para o DTO.
Isso pode reduzir o trabalho de materialização de entidades, dependendo da consulta e do modelo utilizado.
É importante não interpretar essa organização como uma garantia automática de desempenho. O resultado depende da consulta gerada, dos índices, do volume de dados e de outros fatores da aplicação.
Um endpoint pode receber uma solicitação HTTP e encaminhá-la ao handler apropriado.
Por exemplo, a aplicação pode expor uma rota para consultar os detalhes de um pedido e outra para alterar seu estado.
Em uma implementação maior, os handlers podem ser registrados na injeção de dependência e acessados por serviços de aplicação ou por um mecanismo de despacho de comandos e consultas.
Bibliotecas como MediatR podem facilitar esse despacho, mas não são obrigatórias para implementar CQRS.
O padrão está relacionado à separação entre as responsabilidades de leitura e escrita, não à utilização de uma biblioteca específica.
MediatR é uma biblioteca .NET que implementa um mecanismo de comunicação baseado em mensagens dentro da aplicação.
Ela pode ser utilizada para encaminhar comandos e consultas aos respectivos handlers.
Por exemplo, em uma aplicação que utiliza esse modelo, um comando pode ser enviado a um mediador, que encontra o handler registrado para processá-lo.
Isso reduz a necessidade de o componente que recebe a solicitação conhecer diretamente a implementação responsável pela operação.
Entretanto, CQRS e MediatR não são sinônimos.
É possível implementar CQRS sem MediatR, assim como é possível utilizar MediatR para organizar operações sem adotar uma separação completa entre modelos de leitura e escrita.
A biblioteca também não transforma automaticamente uma aplicação em uma arquitetura desacoplada. Os limites entre os componentes e as responsabilidades de cada classe continuam dependendo do projeto.
A escolha deve considerar a complexidade do sistema e os benefícios que o mecanismo de despacho realmente oferece.
A separação entre comandos e consultas facilita a identificação do objetivo de cada operação.
Um componente responsável por atualizar um pedido não precisa também concentrar a lógica de relatórios e consultas.
Essa divisão pode melhorar a legibilidade do código e ajudar a organizar casos de uso com responsabilidades diferentes.
O modelo de leitura pode ser estruturado para retornar exatamente os dados necessários para cada funcionalidade.
Em vez de carregar uma entidade completa e depois transformar seus dados, uma consulta pode projetar diretamente o DTO esperado.
Isso é especialmente útil em telas que combinam informações de várias entidades ou precisam apresentar dados agregados.
Quando as necessidades de leitura e escrita são diferentes, a aplicação pode utilizar estratégias distintas para cada fluxo.
O lado de escrita pode priorizar integridade transacional e aplicação das regras de negócio.
O lado de leitura pode priorizar consultas específicas, projeções e estruturas adequadas aos relatórios.
Em implementações mais avançadas, os dois lados podem utilizar tecnologias de armazenamento diferentes.
Comandos podem representar ações relevantes do domínio, como confirmar um pagamento, aprovar uma proposta ou cancelar um pedido.
Isso pode tornar a intenção da operação mais explícita do que uma sequência genérica de atualizações de propriedades.
A abordagem é especialmente útil quando cada ação possui regras e consequências específicas.
Quando a implementação separa os modelos de leitura e escrita de maneira adequada, pode ser possível dimensionar cada lado de forma independente.
Por exemplo, uma aplicação que recebe muitas consultas e poucas alterações pode utilizar recursos específicos para atender à carga de leitura.
Entretanto, essa vantagem não surge automaticamente ao criar classes separadas.
A escalabilidade independente depende da arquitetura de implantação, dos recursos de infraestrutura e de como os dados são disponibilizados a cada modelo.
O CQRS pode melhorar a organização do código, mas também introduz custos que precisam ser considerados.
Mesmo uma operação simples pode passar a envolver um objeto de comando, um handler, um DTO e outros componentes auxiliares.
Em projetos pequenos, essa estrutura pode aumentar o volume de código sem resolver um problema relevante.
Uma aplicação que realiza operações CRUD simples pode não precisar desse nível de separação.
Quando cada operação possui vários componentes, a equipe precisa compreender como eles se relacionam.
Se a arquitetura não tiver convenções claras, será possível encontrar handlers excessivamente grandes, validações duplicadas e regras distribuídas por diferentes camadas.
A simples adoção de CQRS não garante um código melhor.
Quando os modelos de leitura e escrita utilizam armazenamentos separados e são sincronizados de forma assíncrona, uma consulta pode retornar informações que ainda não refletem a última alteração.
Imagine que um usuário confirme um pedido.
A gravação é concluída, mas a projeção utilizada na tela ainda não foi atualizada.
Durante um curto período, a consulta pode apresentar o estado anterior.
Esse comportamento precisa ser considerado no desenho da experiência do usuário e nas regras do negócio.
Nem toda aplicação pode aceitar esse atraso.
Implementações distribuídas de CQRS podem utilizar filas e eventos para atualizar os modelos de leitura.
Nesse cenário, é necessário tratar falhas de processamento, mensagens duplicadas, novas tentativas e recuperação de mensagens que não foram processadas corretamente.
Também é preciso considerar como garantir que uma alteração no banco de dados e a publicação do evento correspondente não resultem em inconsistências.
O padrão Transactional Outbox pode ajudar nesse problema, registrando a alteração e o evento na mesma transação local, para que a publicação ocorra posteriormente.
Essa solução também exige um processo confiável de publicação e consumidores preparados para lidar com entregas repetidas.
Portanto, a introdução de mensageria deve ser uma decisão consciente, não uma consequência automática da adoção de CQRS.
O padrão tende a ser mais interessante quando existe uma diferença concreta entre as necessidades das operações de leitura e escrita.
Aplicações financeiras, sistemas de pedidos, plataformas de gestão e outros sistemas corporativos podem possuir operações que envolvem várias regras de validação.
Um comando como ApproveContract pode representar uma ação de negócio com condições específicas, enquanto uma consulta apresenta os contratos e seus respectivos estados.
Essa separação pode ajudar a manter cada responsabilidade organizada.
Considere uma plataforma que precisa apresentar dashboards com dados de milhares de operações.
As consultas podem exigir agregações, filtros, agrupamentos e combinações de informações.
Ao mesmo tempo, as operações que modificam os dados precisam preservar as regras transacionais.
CQRS pode permitir que essas necessidades sejam tratadas separadamente.
Entretanto, antes de introduzir uma arquitetura mais complexa, vale avaliar se índices, projeções SQL, otimizações de consultas e cache resolvem o problema.
Uma entidade de domínio nem sempre possui o formato ideal para todas as telas.
Uma tela de pedidos pode precisar combinar dados do cliente, do pagamento e do estado de entrega.
Em vez de carregar vários objetos e realizar transformações complexas na aplicação, o modelo de leitura pode produzir uma projeção adequada ao consumo.
Essa separação pode simplificar tanto o código quanto o acesso aos dados.
Se o volume de consultas é muito superior ao volume de alterações, pode ser interessante avaliar uma arquitetura em que os recursos destinados à leitura sejam dimensionados de forma independente.
Esse cenário costuma ser mais relevante em sistemas com grande volume de tráfego ou requisitos específicos de desempenho.
Ainda assim, a decisão deve ser sustentada por medições e necessidades reais.
Alguns sistemas são organizados em torno de ações que possuem significado claro para o negócio.
Aprovar uma proposta, reservar um quarto, confirmar uma compra ou encerrar um contrato são exemplos de operações que podem envolver diversas regras.
Nesses casos, comandos específicos ajudam a expressar a intenção da operação e a concentrar suas validações.
CQRS não precisa ser a arquitetura padrão de todas as aplicações .NET.
Se o sistema apenas cadastra, consulta, atualiza e exclui registros, com poucas regras de negócio, a separação completa entre comandos e consultas pode não trazer benefícios significativos.
Uma arquitetura com serviços bem definidos, EF Core e DTOs pode ser suficiente.
Em aplicações pequenas, a simplicidade costuma ser uma vantagem importante.
Criar handlers para operações triviais pode aumentar o número de arquivos e tornar o fluxo de execução menos direto.
O custo de manutenção da arquitetura precisa ser proporcional ao problema que ela pretende resolver.
A consistência eventual não é obrigatória em todas as implementações de CQRS, mas pode surgir quando os modelos são atualizados de forma assíncrona.
Se uma operação depende de que a próxima consulta reflita imediatamente a alteração anterior, é necessário projetar cuidadosamente o fluxo.
Isso pode ser resolvido com modelos compartilhados, leitura no armazenamento transacional ou outras estratégias, dependendo da aplicação.
O importante é não introduzir uma arquitetura de sincronização assíncrona sem compreender suas consequências.
CQRS exige convenções, testes e organização para continuar trazendo benefícios.
Se a equipe não enfrenta problemas de separação de responsabilidades, desempenho de consultas ou complexidade do domínio, adotar o padrão pode significar apenas acrescentar uma camada de abstração.
Nesse caso, uma implementação mais simples pode ser a melhor escolha.
Sim.
Clean Architecture e CQRS tratam de preocupações diferentes e podem ser combinados.
A Clean Architecture organiza os limites e as dependências entre as partes da aplicação.
CQRS separa as responsabilidades de leitura e escrita.
Em uma solução .NET, a camada Application pode conter comandos, consultas e seus respectivos handlers. A camada Domain pode concentrar as regras de negócio, enquanto a Infrastructure implementa o acesso aos dados e as integrações externas.
A API recebe as requisições e encaminha os casos de uso.
Entretanto, essa combinação não é obrigatória.
Uma aplicação pode utilizar Clean Architecture sem CQRS, CQRS sem implementar uma Clean Architecture completa ou uma estrutura mais simples que atenda às suas necessidades.
A decisão deve partir da complexidade do domínio, dos requisitos de desempenho e dos custos de manutenção.
Não.
Embora os dois padrões possam ser combinados, eles resolvem problemas diferentes.
CQRS separa as operações que modificam os dados das operações que os consultam.
Event Sourcing é uma abordagem de persistência na qual as mudanças do sistema são registradas como uma sequência de eventos, em vez de armazenar apenas o estado atual como fonte principal de informação.
Uma aplicação pode utilizar CQRS sem Event Sourcing.
Também é possível implementar Event Sourcing em conjunto com CQRS, utilizando os eventos registrados para atualizar modelos de leitura.
Essa combinação pode ser útil quando o histórico completo das alterações possui valor para o negócio, mas aumenta consideravelmente a complexidade da solução.
Antes de adotar os dois padrões, é importante compreender as necessidades de auditoria, recuperação de estado, consistência e processamento de eventos.
Antes de implementar CQRS em uma aplicação .NET, considere as seguintes perguntas:
As operações de leitura e escrita possuem necessidades realmente diferentes?
As regras de negócio tornam as operações de escrita significativamente complexas?
As consultas apresentam problemas que não podem ser resolvidos de forma mais simples?
Existe uma necessidade concreta de otimizar os modelos de leitura e escrita separadamente?
A equipe está preparada para manter a estrutura adicional?
Se houver armazenamento separado, como será tratada a sincronização dos dados?
Os benefícios esperados podem ser medidos por testes ou indicadores de desempenho?
Se as respostas apontarem para problemas reais, CQRS pode ser uma boa escolha.
Caso contrário, uma aplicação tradicional bem organizada provavelmente será suficiente.
Também é possível começar com uma separação lógica entre comandos e consultas e evoluir para modelos ou armazenamentos separados apenas quando houver necessidade comprovada.
Essa abordagem reduz o risco de introduzir complexidade prematuramente.
CQRS é um padrão arquitetural que pode trazer benefícios importantes para aplicações .NET, especialmente quando as operações de leitura e escrita possuem responsabilidades, regras ou requisitos de desempenho diferentes.
Ao separar comandos e consultas, a aplicação pode organizar melhor seus casos de uso, produzir modelos de leitura específicos e evoluir cada fluxo de acordo com suas necessidades.
Entretanto, o padrão também acrescenta componentes e pode introduzir problemas de consistência e sincronização quando utilizado em arquiteturas distribuídas.
Por isso, CQRS não deve ser adotado apenas para seguir uma tendência ou porque uma biblioteca facilita a criação de handlers.
Em aplicações CRUD simples, uma arquitetura tradicional pode oferecer melhor equilíbrio entre clareza e manutenção.
Em sistemas corporativos com regras complexas, consultas exigentes e necessidades distintas de leitura e escrita, CQRS merece uma avaliação mais cuidadosa.
A melhor estratégia é começar pelo problema, avaliar as alternativas mais simples e adotar o padrão quando seus benefícios justificarem os custos adicionais.