Uma tela precisa responder rápido.

Uma regra de negócio precisa proteger invariantes.

Essas duas necessidades parecem pertencer ao mesmo modelo até o sistema crescer.

No começo, uma tabela resolve tudo. A aplicação grava nela, consulta nela, monta tela a partir dela, gera relatório a partir dela e ainda tenta publicar eventos quando alguma coisa muda.

Funciona enquanto o domínio é simples e o volume é baixo.

Depois aparecem sintomas conhecidos.

A consulta que a tela precisa fazer exige join demais. O relatório pressiona a mesma tabela usada pela transação. O endpoint de escrita começa a carregar dados que só existem para montar resposta. A modelagem fica dividida entre proteger regra de negócio e agradar interface. O banco vira, ao mesmo tempo, fonte de verdade, índice de busca, cache, relatório e contrato público.

CQRS, Command Query Responsibility Segregation, aparece nesse ponto.

Não para deixar o desenho mais bonito.

Mas para admitir que escrever e ler muitas vezes são problemas diferentes.


Comando não é consulta

Um comando expressa uma intenção de mudança.

ApprovePayment.

CancelOrder.

ReserveStock.

Ele precisa passar por validação, regra de negócio, controle de concorrência e persistência consistente.

Uma consulta faz outra pergunta.

"Quais pedidos estão atrasados?"

"Qual é o resumo da compra do cliente?"

"Quais produtos aparecem no painel operacional?"

Ela precisa entregar uma visão rápida, filtrável e adequada para consumo.

Quando o mesmo modelo tenta atender bem esses dois lados, ele começa a acumular compromissos ruins.

O modelo de escrita fica cheio de campos derivados porque a tela precisa deles.

O modelo de leitura fica dependente de estruturas normalizadas que existem para preservar consistência na escrita.

O time passa a otimizar uma consulta e descobre que acabou enfraquecendo uma regra.

Ou melhora a regra e descobre que a tela ficou cara demais.

CQRS separa responsabilidades: um modelo decide mudanças; outro modelo responde perguntas.

Essa separação pode ser pequena, dentro da mesma aplicação.

Também pode ser grande, com bancos diferentes, filas, tópicos e projeções assíncronas.

O padrão não exige distribuição.

Ele exige clareza.


O modelo de escrita protege invariantes

O lado de escrita precisa responder a uma pergunta central:

"essa mudança pode acontecer agora?"

Para cancelar um pedido, talvez o sistema precise saber se ele já foi enviado.

Para aprovar um pagamento, talvez precise garantir que o valor bate com o pedido.

Para reservar estoque, talvez precise impedir que duas compras consumam a mesma unidade.

Esse modelo não deveria ser desenhado primeiro pensando na tela mais conveniente.

Ele deveria ser desenhado para manter o domínio coerente.

Em um sistema de pedidos, o agregado de escrita pode ser compacto. Ele pode carregar apenas o necessário para decidir transições válidas.

Ele não precisa saber o nome formatado do cliente, o histórico completo de navegação, o SLA renderizado em português ou a lista de recomendações que aparece no frontend.

Essas coisas podem ser úteis para leitura.

Mas colocar tudo no mesmo modelo aumenta acoplamento e torna cada mudança mais pesada do que deveria.

No lado de escrita, menos pode ser mais.

Menos dados carregados.

Menos joins.

Menos dependência de detalhes visuais.

Mais foco em regra, consistência e intenção.


O modelo de leitura materializa perguntas

O lado de leitura não precisa parecer com o domínio interno.

Ele precisa responder bem às perguntas que o produto faz.

Uma tela de acompanhamento de pedidos talvez precise mostrar:

order_id
customer_name
current_status
payment_status
delivery_deadline
last_event_at
has_pending_action

Essa estrutura pode ser péssima como fonte de verdade.

Mas pode ser excelente como projeção.

Ela evita joins em tempo de consulta, organiza dados no formato da interface e reduz pressão sobre o modelo transacional.

O ponto importante é reconhecer que essa projeção é derivada.

Ela pode atrasar.

Pode ser reconstruída.

Pode ser descartada se corromper.

Pode existir em várias versões para necessidades diferentes.

Um painel operacional pode usar uma tabela desnormalizada.

Uma busca pode usar Elasticsearch ou OpenSearch.

Um relatório pode usar um banco analítico.

Uma API pública pode usar um documento pronto para serialização.

CQRS permite que cada leitura seja honesta sobre sua finalidade, em vez de forçar todo consumidor a atravessar o mesmo modelo de escrita.


Kafka entra como transporte de fatos

Kafka costuma aparecer em arquiteturas CQRS porque existe uma fronteira natural entre escrita e leitura.

O lado de escrita aceita um comando, aplica regras, persiste uma mudança e publica um evento.

Consumidores leem esse evento e atualizam projeções de leitura.

O fluxo fica mais ou menos assim:

Command -> Write Model -> Event -> Kafka -> Read Projection

Isso combina com o que a série já discutiu sobre eventos como contrato, Outbox Pattern e Event Sourcing.

Mas Kafka não transforma automaticamente um sistema em CQRS.

Se o evento é pobre, a projeção fica dependente de consultas extras.

Se a chave é errada, a ordem relevante pode se perder.

Se o consumidor não é idempotente, uma duplicidade corrompe a leitura.

Se não existe estratégia de replay, a projeção vira um cache caro e frágil.

Kafka ajuda a distribuir fatos.

A arquitetura ainda precisa decidir quais fatos existem, qual projeção eles alimentam e como reconstruir essa projeção quando algo falhar.


Consistência eventual não é desculpa

Separar leitura e escrita frequentemente cria consistência eventual.

O comando foi aceito.

O evento foi publicado.

A projeção ainda não atualizou.

Durante alguns segundos, a tela pode mostrar o estado antigo.

Isso não é necessariamente um problema.

Mas precisa ser uma decisão explícita.

Se o usuário acabou de cancelar um pedido, talvez a interface precise mostrar uma confirmação baseada no resultado do comando, mesmo que a lista ainda esteja atrasada.

Se um operador precisa tomar decisões críticas em tempo real, talvez a projeção tenha um limite de atraso muito baixo.

Se um relatório financeiro roda no fim do dia, alguns minutos podem ser aceitáveis.

O erro é vender consistência eventual como uma abstração invisível.

Ela aparece na experiência do usuário, nos testes, no suporte, nos alertas e no debugging.

Uma projeção atrasada não deveria parecer uma regra de negócio quebrada.

O sistema precisa saber explicar:

  • quando a escrita foi aceita;
  • qual evento foi gerado;
  • em que offset a projeção parou;
  • qual consumidor está atrasado;
  • se o dado exibido é recente o suficiente para aquela decisão.

Sem isso, CQRS vira uma fábrica de dúvidas.


CQRS não exige Event Sourcing

Uma confusão comum é tratar CQRS e Event Sourcing como se fossem o mesmo padrão.

Eles combinam bem, mas são independentes.

CQRS separa modelos de comando e consulta.

Event Sourcing define que a fonte de verdade é a sequência de eventos.

Você pode ter CQRS com um banco relacional tradicional no lado de escrita e eventos publicados por outbox para alimentar projeções.

Você também pode ter Event Sourcing sem várias projeções sofisticadas, embora isso costume limitar parte do ganho.

A pergunta prática é outra:

"a leitura precisa de um modelo diferente da escrita?"

Se sim, CQRS pode ajudar.

"a história de fatos precisa ser a fonte de verdade?"

Se sim, Event Sourcing pode entrar na conversa.

Misturar os dois sem necessidade costuma aumentar a complexidade de uma vez só: comandos, eventos, replay, snapshots, projeções, consistência eventual, versionamento, observabilidade e operação.

Às vezes esse custo vale.

Às vezes é só arquitetura demais para um problema pequeno.


Quando faz sentido

CQRS costuma fazer sentido quando leitura e escrita têm perfis muito diferentes.

Muitas consultas para poucas escritas.

Telas que precisam de dados agregados de várias origens.

Relatórios que não deveriam pressionar o banco transacional.

Domínios com regras fortes na escrita e leituras altamente otimizadas.

Integrações em que vários consumidores precisam reagir aos mesmos fatos.

Também faz sentido quando uma única modelagem começa a prejudicar os dois lados.

O time adiciona coluna derivada para acelerar tela.

Depois adiciona trigger para manter a coluna.

Depois cria job de correção.

Depois descobre que a regra de negócio depende daquele dado derivado.

Esse é um sinal de que leitura e escrita já estão separadas na prática, mas de forma acidental.

CQRS torna essa separação explícita e operacionalmente administrável.


Quando não faz sentido

Nem todo CRUD precisa de CQRS.

Cadastro simples, baixa concorrência, consultas diretas e regras pequenas normalmente funcionam melhor com um modelo único.

Separar leitura e escrita cedo demais cria trabalho que o domínio ainda não justificou.

Você passa a manter projeções, monitorar consumidores, lidar com atraso, planejar rebuild, versionar eventos e explicar por que a tela não atualizou imediatamente.

Se o problema real é apenas uma consulta lenta, talvez um índice resolva.

Se o problema é uma tela pesada, talvez uma view materializada, cache ou endpoint específico baste.

Se o problema é acoplamento entre serviços, CQRS sozinho não corrige contrato ruim.

O padrão é útil quando existe uma tensão real entre comandar mudanças e responder consultas.

Sem essa tensão, ele vira cerimônia.


O ponto que vale fixar

CQRS não é sinônimo de microsserviços.

Não é sinônimo de Kafka.

Não é sinônimo de Event Sourcing.

É uma decisão de modelagem: separar o lado que muda o sistema do lado que observa o sistema.

Essa separação vale quando o modelo único começa a cobrar caro demais.

Kafka pode ser uma ótima ponte entre escrita e leitura, desde que os eventos sejam contratos reais, os consumidores sejam idempotentes e as projeções possam ser observadas e reconstruídas.

O ganho não está em ter dois modelos.

O ganho está em parar de forçar leitura e escrita a fingirem que têm o mesmo problema.