CQRS: separando leituras e escritas
1. Introdução ao CQRS
O padrão CQRS (Command Query Responsibility Segregation) foi formalmente introduzido por Greg Young e popularizado por Martin Fowler. Sua premissa central é simples, mas poderosa: separar os modelos e operações de leitura (queries) dos de escrita (commands). Em sistemas tradicionais, um mesmo modelo de domínio é usado tanto para ler quanto para modificar dados, o que frequentemente gera compromissos indesejados.
A diferença fundamental reside na natureza dos comandos e consultas. Comandos representam intenções de mudança de estado — eles devem ser validados, podem gerar efeitos colaterais e não retornam dados de domínio. Consultas, por outro lado, recuperam dados sem alterar o estado do sistema. Ao segregar essas responsabilidades, o CQRS resolve problemas como modelos de domínio excessivamente complexos (que tentam servir a múltiplos propósitos) e gargalos de escalabilidade onde leituras e escritas competem pelos mesmos recursos.
2. Fundamentos: Comandos e Consultas
Comandos são objetos que encapsulam uma intenção de alterar o estado do sistema. Eles são nomeados no imperativo (ex: CriarPedido, AtualizarEstoque) e devem conter apenas os dados necessários para a operação. Um comando não retorna dados de domínio — no máximo, retorna um identificador ou confirmação de sucesso.
Exemplo de comando:
Command: CriarPedido
{
clienteId: "123",
itens: [
{ produtoId: "456", quantidade: 2 }
],
enderecoEntrega: "Rua A, 100"
}
Consultas são operações que retornam dados sem modificar o estado. Elas são otimizadas para a forma como os dados serão exibidos ou processados. Uma consulta pode retornar DTOs (Data Transfer Objects) específicos para cada caso de uso.
Exemplo de consulta:
Query: ObterResumoPedido
{
pedidoId: "789"
}
// Retorna: { status, valorTotal, itensResumidos }
A separação de modelos significa que o modelo de escrita é rico em regras de negócio e validações, enquanto o modelo de leitura é desnormalizado e otimizado para consultas rápidas.
3. Arquitetura e Componentes do CQRS
A arquitetura CQRS introduz duas camadas principais:
Camada de Comando:
- Command Bus: roteia comandos para seus respectivos handlers
- Command Handlers: contêm a lógica de validação e aplicação das regras de negócio
- Validação: garante que o comando atenda aos pré-requisitos antes da execução
Camada de Consulta:
- Query Handlers: executam consultas otimizadas no banco de leitura
- Projeções: dados desnormalizados prontos para consumo
- DTOs: estruturas de dados específicas para cada tela ou relatório
O armazenamento é geralmente separado:
- Banco de escrita: normalizado, otimizado para integridade e operações transacionais
- Banco de leitura: desnormalizado, com tabelas planas e índices otimizados para consultas
4. Sincronização entre Modelos de Leitura e Escrita
A sincronização entre os modelos é feita através de projeções. Existem duas estratégias principais:
Projeção síncrona: após o comando ser processado, o banco de leitura é atualizado imediatamente na mesma transação. Isso garante consistência forte, mas pode impactar a performance de escrita.
Projeção assíncrona: o comando é processado e um evento de domínio é publicado. Um subscriber processa esse evento e atualiza o banco de leitura de forma independente. Isso introduz consistência eventual, mas oferece melhor desempenho e resiliência.
Exemplo de fluxo assíncrono:
1. Comando "CriarPedido" chega ao Command Bus
2. Command Handler valida e persiste no banco de escrita
3. Handler publica evento "PedidoCriado"
4. Evento é processado por um Projector
5. Projector atualiza a projeção no banco de leitura
6. Cliente pode consultar os dados eventualmente consistentes
A consistência eventual exige que o sistema tolere atrasos entre a escrita e a leitura, o que é aceitável em muitos cenários de negócio.
5. Benefícios e Desafios do CQRS
Benefícios:
- Escalabilidade independente: é possível escalar os serviços de leitura e escrita separadamente
- Otimização de desempenho: cada modelo é ajustado para seu propósito específico
- Equipes paralelas: times diferentes podem trabalhar nos modelos de leitura e escrita
- Segurança: é possível aplicar políticas de acesso distintas para leituras e escritas
Desafios:
- Complexidade adicional: a arquitetura exige mais componentes e coordenação
- Duplicação de dados: os mesmos dados podem existir em múltiplos formatos e locais
- Gerenciamento de consistência: a consistência eventual requer mecanismos de reconciliação
- Maior custo operacional: mais bancos de dados e serviços para gerenciar
Quando aplicar CQRS:
- Sistemas com alta carga de leitura assimétrica em relação à escrita
- Domínios complexos onde o modelo de escrita precisa ser rico e o de leitura simples
- Cenários onde diferentes representações dos mesmos dados são necessárias para diferentes casos de uso
6. CQRS sem Event Sourcing (CQRS Puro)
É possível implementar CQRS sem Event Sourcing, utilizando bancos de dados relacionais tradicionais. Nessa abordagem, o banco de escrita armazena o estado atual das entidades, e o banco de leitura armazena projeções desnormalizadas.
Exemplo de fluxo completo (CQRS puro):
// Escrita
1. Cliente envia comando "AtualizarEmailUsuario"
{ usuarioId: 1, novoEmail: "novo@email.com" }
2. Command Handler valida regras:
- Novo email não está em uso
- Formato de email é válido
3. Handler persiste no banco de escrita:
UPDATE usuarios SET email = 'novo@email.com' WHERE id = 1;
4. Handler publica evento "EmailUsuarioAtualizado"
{ usuarioId: 1, emailAnterior: "antigo@email.com", novoEmail: "novo@email.com" }
// Leitura (assíncrona)
5. Projector recebe o evento e atualiza a projeção:
UPDATE proj_usuarios_resumo SET email = 'novo@email.com' WHERE usuarioId = 1;
6. Consulta "ObterResumoUsuario" agora retorna os dados atualizados
A diferença para Event Sourcing é que o banco de escrita não armazena o histórico completo de eventos, apenas o estado atual. Isso simplifica a implementação, mas sacrifica a capacidade de reconstruir estados passados.
7. Padrões Relacionados e Considerações Finais
O CQRS se relaciona fortemente com Domain-Driven Design (DDD) e seus bounded contexts. Cada contexto delimitado pode ter seu próprio modelo de leitura e escrita, respeitando a linguagem ubíqua do domínio.
Comparado ao CRUD tradicional, o CQRS oferece mais flexibilidade para cenários complexos, mas introduz complexidade desnecessária em sistemas simples. Já o Event Sourcing complementa o CQRS naturalmente, mas não é um requisito obrigatório.
Boas práticas:
- Versionamento de projeções: evolua as projeções de leitura de forma independente, permitindo migrações graduais
- Monitoramento de consistência: implemente métricas para detectar atrasos na sincronização entre modelos
- Comece simples: evite CQRS em sistemas com domínios triviais ou baixa carga assimétrica
O CQRS é uma ferramenta poderosa quando aplicada nos contextos corretos. Sua adoção deve ser motivada por necessidades reais de escalabilidade, complexidade de domínio ou requisitos de desempenho, e não por modismo arquitetural.
Referências
- Martin Fowler - CQRS — Artigo seminal que define o padrão e seus fundamentos
- Microsoft - CQRS Pattern — Documentação oficial da Microsoft sobre implementação do padrão em arquiteturas cloud
- Greg Young - CQRS Documents — Documento original de Greg Young detalhando o padrão e suas variações
- Udi Dahan - Clarified CQRS — Artigo técnico que esclarece conceitos comuns e equívocos sobre o padrão
- AWS - CQRS Pattern — Guia prático da AWS sobre implementação de CQRS com serviços cloud
- Khalil Stemmler - CQRS in Practice — Tutorial prático de implementação de CQRS com exemplos em TypeScript