Em arquiteturas de microsserviços e sistemas de alta escala, um dos gargalos mais comuns surge no banco de dados. O padrão de projeto tradicional, focado em CRUD (Create, Read, Update, Delete), utiliza a mesma estrutura e as mesmas tabelas tanto para gravar dados quanto para consultá-los.
Conforme a aplicação cresce, essa abordagem unificada começa a dar sinais de cansaço: consultas analíticas com múltiplos JOINs pesados disputam recursos com inserções e atualizações críticas, gerando contenção de locks, alta latência e degradação geral de performance.
Para resolver esse problema estrutural, o padrão CQRS (Command Query Responsibility Segregation) propõe uma mudança fundamental de mentalidade. Neste artigo, é explicado os principais insights do "O guia completo de CQRS em como resolver gargalos no banco de dados".
O problema do modelo único (CRUD Tradicional)
No modelo tradicional, o mesmo modelo de dados serve a dois propósitos conflitantes:
- Escrita: Requer validação rígida de regras de negócio, dados normalizados (3NF) para evitar redundância e garantir consistência transacional (ACID).
- Leitura: Requer dados estruturados de forma simples, prontos para exibição na tela (UI/API), idealmente desnormalizados para evitar cruzamentos de tabelas dispendiosos.
Quando tentamos otimizar uma tabela para leitura (criando múltiplos índices, por exemplo), prejudicamos o desempenho das escritas. Quando otimizamos para escrita (normalizando excessivamente), tornamos as consultas lentas.
O Conceito de CQRS
O CQRS separa a aplicação em duas responsabilidades distintas:
1. Commands (Comandos / Escrita)
Objetivo: Alterar o estado do sistema (CriarPedido, AlterarEndereco).
Característica: Focado no domínio e na integridade das regras de negócio. Retorna apenas status de sucesso/falha, nunca o payload completo de dados.
Modelo de Dados: Normalizado e otimizado para gravações rápidas e consistentes.
2. Queries (Consultas / Leitura)
Objetivo: Retornar dados otimizados para apresentação.
Característica: Não altera o estado do sistema e não executa regras de negócio complexas.
Modelo de Dados: Desnormalizado, pré-formatado ou materializado exatamente na estrutura que o cliente (Front-end/Mobile) precisa consumir.
Principais Benefícios Arquiteturais
| Benefício | Descrição |
| Escalabilidade Independente | Você pode escalar a infraestrutura de leitura (ex: réplicas de leitura, instâncias de cache ou NoSQL) de forma totalmente independente da base de escrita. |
| Alta Performance nas Consultas | Elimina a necessidade de JOINs complexos em tempo de execução, pois os dados de leitura já estão projetados e prontos no banco de consultas. |
| Código de Domínio Mais Limpo | O lado de escrita fica limpo de código referente a paginação, buscas avançadas e formatações para telas. |
| Segurança e Isolamento | Permite conceder permissões diferenciadas em nível de infraestrutura para operações de leitura e escrita. |
Cuidados e Armadilhas: Quando NÃO usar
Embora seja um padrão poderoso, o CQRS introduz complexidade acidental se aplicado no contexto errado.
Regra de ouro: Não utilize CQRS para aplicações simples, CRUDs convencionais ou domínios onde o volume de leituras e escritas seja baixo e equilibrado.
Principais desafios ao adotar CQRS:
- Consistência Eventual (Eventual Consistency): Se o modelo de leitura for atualizado de forma assíncrona (via mensageria e eventos de domínio), haverá um pequeno intervalo de latência até que o dado gravado no lado de escrita reflita na base de consulta.
- Duplicação de Código e Infraestrutura: Exige manter dois modelos distintos e, eventualmente, duas bases de dados diferentes (ex: PostgreSQL para escrita e MongoDB/Elasticsearch para leitura).
- Sincronização de Dados: É necessário implementar e monitorar mecanismos tolerantes a falhas para garantir que os eventos de atualização cheguem à base de leitura.
Exemplo Prático de Código
Abaixo apresentamos um exemplo conceitual em TypeScript demonstrando a separação das responsabilidades de escrita (Command) e leitura (Query):
// Command Handler - Responsável por alterar o estado
class CriarPedidoHandler {
async handle(command: CriarPedidoCommand): Promise<void> {
// 1. Valida regras de negócio do domínio
const pedido = new Pedido(command.clienteId, command.itens);
// 2. Persiste no modelo de escrita (Write Model - Relacional/Normalizado)
await this.pedidoRepository.save(pedido);
// 3. Publica evento para sincronização assíncrona
await this.eventBus.publish(new PedidoCriadoEvent(pedido));
}
}
// Query Handler - Responsável por ler dados otimizados
class ObterDetalhesPedidoHandler {
async handle(query: ObterPedidoQuery): Promise<PedidoDTO> {
// Consulta diretamente a base de leitura (Read Model desnormalizado)
return await this.readDatabase.findPedidoById(query.pedidoId);
}
}
Considerações finais
O CQRS é uma ferramenta arquitetural indispensável quando atingimos os limites do banco de dados relacional tradicional sob alta carga. Ele resolve o gargalo de I/O ao permitir que a leitura e a escrita cresçam de forma independente, mas exige maturidade da equipe para lidar com consistência eventual e sistemas distribuídos.
Feito!