CQRS e divisão de leitura/escrita
O que é CQRS
O CQRS (Command Query Responability Segregation) é uma abordagem arquitetônica que separa o modelo de dados e os componentes de gravação (commands) e leitura (queries).
A ideia é que o processo de alteração de estado seja otimizado para invariantes e transações validadas, e a leitura para projeções rápidas, com destino e escala.
Para quê é preciso
Desempenho de leitura: projeções materializadas sob cenários específicos (fitas, relatórios, diretórios).
Estabilidade da via crítica, a gravação está isolada dos joins e equipamentos pesados.
Escolha de armazenamento: OLTP para gravação, OLAP/kesh/motores de busca para leitura.
Evolução acelerada: Adicione novas visões sem o risco de «quebrar» transações.
Observabilidade e auditoria (especialmente na ligação com o Event Surcing): é mais fácil restaurar e reinventar o estado.
Quando aplicar (e quando não aplicar)
Adequado se:- As leituras com diferentes cortes de dados e agregação complexa prevalecem.
- O caminho crítico de gravação deve ser fino e previsível.
- Você precisa de SLO/SLA diferentes para leitura e gravação.
- É necessário isolar a lógica de registro do domínio das necessidades analíticas/de busca.
- O domínio é simples, a carga é baixa; A CRUD está a lidar.
- Uma forte coerência entre leitura e gravação é obrigatória para todos os cenários.
- A equipa é inexperiente e a complexidade operacional é inaceitável.
Conceitos básicos
Comando (Command): a intenção é alterar o status ('CreateOrder', 'CapturePayment'). A verificar os invariantes.
Consulta (Query): obtenção de dados ('GetOrderById', 'ListUserTransactions'). Sem efeitos colaterais.
Modelo de gravação: unidades/invariantes/transações; armazenamento - relacional/chave-valor/logs de evento.
Modelo de leitura (projeções): tabelas/índices/kesh materializadas, sincronizadas de forma asincrona.
Coerência: muitas vezes, o eventual entre a gravação e a leitura; caminhos críticos - através da leitura direta do modelo write.
Arquitetura (esqueleto)
1. Serviço Write: aceita comandos, valida invariantes, registra alterações (BB ou eventos).
2. Outbox/CDC: publicação garantida de alterações.
3. Processadores de projeção: escutam eventos/CDC e atualizam modelos read.
4. Serviço de read: serve querias a partir de visualizações materializadas/cajas/buscas.
5. Sagas/Orquestra: Coordenam processos cruzados.
6. Observabilidade: lâmina de projeções, porcentagem de aplicações bem sucedidas, DLQ.
Projetar modelo de gravação
Unidades: limites de transação nítidos (por exemplo, «Order», «Payment», «UserBalance»).
Invariantes: formalize (somas em dinheiro ≥ 0, exclusividade, limites).
Os comandos são idimpotentes à chave (por exemplo, 'idempotency _ key').
Transações são mínimas em abrangência; Efeitos secundários externos - via outbox.
Exemplo de comando (pseudo-JSON)
json
{
"command": "CapturePayment",
"payment_id": "pay_123",
"amount": 1000,
"currency": "EUR",
"idempotency_key": "k-789",
"trace_id": "t-abc"
}
Projetar modelo de leitura
Afaste as solicitações: quais telas/relatórios são necessários?
A denormização é válida, o modelo read é «dinheiro otimizado».
Várias projeções para diferentes tarefas: pesquisa (OpenSearch), relatórios (armazenamento de coluna), cartões (KV/Redis).
TTL e intersecção: as projeções devem ser capazes de recuperar da origem (replicações de eventos/eventos).
Coerência e UX
Eventual consistency: a interface pode exibir dados antigos brevemente.
Pattern UX: «dados são atualizados»..., UI optimístico, indicadores de sincronização, bloqueio de ações perigosas antes da confirmação.
Para as operações que exigem coerência forte (por exemplo, mostrar o balanço exato antes de cancelar), leia diretamente no modelo write.
CQRS e Event Surcing (opcional)
O Event Surcing (ES) armazena eventos e o estado do aparelho é resultado do seu encolhimento.
A ligação CQRS + ES fornece uma auditoria perfeita e um cruzamento fácil de projeções, mas aumenta a complexidade.
Alternativa: normal OLTP-BD + outbox/CDC → projeção.
Replicação: Outbox e CDC
Outbox (em uma transação): gravação de alterações de domínio + gravação de evento em outbox; O pablicher é entregue no pneu.
CDC: leitura a partir de logs de BD (Debezium etc.) → transformação em eventos de domínio.
Garantia: por padrão at-least-once, os consumidores e projeções devem ser idimpotentes.
Selecionar armazenamento
Write: relacional (PostgreSQL/MySQL) para transações; KV/Photo - onde os invariantes são simples.
Read:- KV/Redis - cartões e leituras-chave rápidas;
- Pesquisa (OpenSearch/Elasticsearch) - pesquisa/filtros/facetas;
- Colunas (ClickHouse/BigQuery) - relatórios;
- Kesh em CDN - diretórios públicos/conteúdo.
Pattern de integração
Camada API: endpoints/serviços individuais para 'commands' e 'quéries'.
Idempotidade: chave da operação no cabeçalho/corpo; armazenamento de recent-keys com TTL.
Sagas/Orquestra: tempo, compensações, repetição de passos.
Backpressure: limitação do paralelismo dos processadores de projeção.
Observabilidade
Métricas write: p95/99 latência de comandos, proporção de transações bem sucedidas, erros de validação.
Métricas de read: p95/99 consultas, hit-rate kesha, carga do cluster de busca.
Projeções (tempo e mensagens), taxa DLQ, porcentagem de deduções.
Tracing: 'trace _ id' passa pelo comando → outbox → projeção de → query.
Segurança e Complacência
Separação de direitos: diferentes scopes/papéis de escrita e leitura; o princípio dos menores privilégios.
PII/PCI: minimize nas projeções; criptografia at-rest/in-flight; camuflagem.
Auditoria: verifique o comando, o ator, o resultado, 'trace _ id'; arquivos WORM para domínios críticos (pagamentos, KYC).
Testes
Contracto testes: para comandos (erros, invariantes) e queries (formatos/filtros).
Project tests: Apresente uma série de eventos/CDC e verifique o modelo read final.
Chaos/latency: injeção de atrasos nos processadores de projeção; verificação de UX na laje.
Replayability: cruzamento de projeções no estande de snapshots/logs.
Migração e evolução
Novos campos - adutor no evento/CDC; modelos read são reencontrados.
Gravação dupla (dual-write) em redesenhos de padrão; Temos projeções antigas até mudar.
Versioning: 'v1 '/' v2' eventos e endpoints, plano sunset.
Função flags: digite novas quéries/projeções de canário.
Antipattern
CQRS «para a moda» em serviços CRUD simples.
Severa dependência sincronizada de leitura da escrita (mata isolamento e resistência).
Um índice para tudo é misturar consultas heterogéneas em uma read-store.
Não há idempotidade nas projeções de duplicação e discrepância.
Projeções não remodeladas (sem replay/snapshot).
Exemplos de domínios
Pagamentos (serviço online)
Write: 'Athorize', 'Capture', 'Refund' no banco de dados de transação; outbox publica 'payment'.
Read:- Redis «cartão de pagamento» para UI;
- ClickHouse para relatórios;
- Para encontrar transações.
- Caminho crítico: autorização ≤ 800 ms p95; coerência de leitura para UI - eventual (até 2-3 c).
KYC
Write: comandos de início/update de status; armazenamento de PII em um BD protegido.
Read: projeção facilitada de estatais sem PII; O PII é puxado por pontos quando necessário.
Segurança: scopes diferentes para leitura de status e acesso a documentos.
Balanços (iGaming/Finanças)
Write: unidade 'UserBalance', com encrencas atômicas/decrementos; chaves idoneais para a cirurgia.
Read: kesh para «equilíbrio rápido»; para cancelar - leitura direta de write (coerência rigorosa).
Os depósitos/conclusões são coordenados por eventos, e as indemnizações por falhas.
Folha de cheque de implementação
- As unidades e invariantes do modelo write são selecionados.
- Quéries-chave foram definidas e projectadas sob elas.
- Os processadores de projeção outbox/CDC e idumpotentes estão configurados.
- Há um plano para cruzar projeções (snapshot/replay).
- SLO: Latidão dos comandos, claridade das projeções, disponibilidade de read/write individualmente.
- As permissões de acesso e a criptografia de dados foram compartilhadas.
- Alertas em DLQ/liga/falhas de dedução.
- Testes: contratos, projeções, caos, réplicas.
FAQ
O Event Surcing é necessário para o CQRS?
Não. Você pode construir em um BD + outbox/CDC convencional.
Como lutar contra a secessão?
Claramente projetar UX, medir projeções de liga, deixar operações críticas ler a partir de write.
É possível manter write e read em um único serviço?
Sim, separação física - opcional; a divisão lógica da responsabilidade é obrigatória.
E as transações entre as unidades?
Através de sagas e eventos; evite transações distribuídas, se possível.
Resultado
O CQRS desenrola as mãos, um caminho de gravação fino e confiável, com invariantes claros e leituras rápidas e de destino a partir de projeções materializadas. Isso aumenta a produtividade, simplifica a evolução e torna o sistema mais resistente às cargas de trabalho - se for disciplinado gerindo coerência, observabilidade e migração.