Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

05 - CQRS com Event Sourcing (Combinação)

#combinacao#cqrs#event-sourcing#projecoes
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione video: https://youtu.be/... no frontmatter da nota.

CQRS com Event Sourcing: A Combinação

A combinação de CQRS com Event Sourcing forma o arranjo mais completo da família orientada a eventos: o log de eventos é a fonte de verdade do lado de escrita, e as projeções materializadas a partir dele são os modelos de leitura. O Event Sourcing dá ao CQRS uma sincronização com semântica de negócio, porque os eventos que alimentam as visões são fatos do domínio, e não linhas alteradas. O CQRS dá ao Event Sourcing a resposta para a pergunta mais cara do padrão, que é consultar estado atual sem reidratar streams inteiros. Os dois padrões não se exigem mutuamente, mas juntos resolvem as principais fraquezas um do outro, ao preço de acumular a complexidade operacional dos dois. Por isso a decisão de combinar deve passar por critérios objetivos de carga, auditoria e evolução, e não por preferência estética. Entender o papel exato de cada peça é o que permite adotar a combinação sem transformar o sistema em um exercício de arquitetura.

Como os dois padrões se encaixam

No lado de escrita, o Event Sourcing define a mecânica: o comando carrega o agregado por replay do stream, valida as regras, gera novos eventos e os anexa ao log com verificação de versão esperada. Nenhum estado corrente é sobrescrito; a mudança é sempre um acréscimo de fato. O agregado, o repositório e o event store formam o núcleo de decisão, e a única saída desse núcleo é o próprio log.

No lado de leitura, o CQRS define a forma: cada consulta relevante recebe uma projeção dedicada, desenhada para a tela ou para o relatório que a consome. As projeções assinam o fluxo global de eventos, aplicam transformações e gravam os resultados em bases otimizadas para leitura, com checkpoints próprios. A direção é única e irreversível: comando gera evento, evento alimenta projeção, projeção serve consulta. Nenhuma leitura escreve de volta, e nenhuma visão é fonte de verdade.

O problema que a combinação resolve

CQRS sozinho deixa aberta a pergunta de como o read model fica sabendo das mudanças. As respostas comuns são gatilhos, dupla gravação no código da aplicação ou captura de dados alterados, e todas têm limites conhecidos: o gatilho replica linhas sem semântica, a dupla gravação corre risco de inconsistência entre duas escritas, e a captura de mudanças reflete tabelas, não fatos. Quando a visão precisa de uma regra de negócio para ser construída, como somar o total de um pedido a partir dos itens, qualquer uma dessas opções exige artifício.

Event Sourcing sozinho deixa aberta a pergunta oposta: como consultar. A leitura de estado exige reidratar o agregado, e leituras de lista, busca ou relatório não têm de onde vir sem varreduras caras de todos os streams. A combinação fecha as duas pontas ao mesmo tempo: os eventos já existem por construção, com semântica completa, e as projeções os transformam em consultas baratas. O custo dessa elegância é pagar a infraestrutura dos dois padrões de uma vez, e é exatamente por isso que a adoção precisa de justificativa medida.

Funcionamento interno: do comando à projeção

O caminho completo de uma mudança passa por etapas bem definidas.

  1. O comando chega ao handler com a identidade do agregado e os dados da intenção.
  2. O agregado é reidratado por replay do stream, ou a partir de snapshot, e a versão atual fica conhecida.
  3. As regras de negócio são executadas e geram eventos pendentes, ou recusam a operação.
  4. Os eventos são anexados ao store com a versão esperada; conflito de concorrência força nova execução.
  5. O projetor recebe o evento pela assinatura do fluxo global, aplica a transformação da visão e avança o checkpoint.
  6. A consulta passa a refletir a mudança, com a defasagem medida e tratada como consistência eventual.
// Lado de escrita: comando carrega o agregado do stream e anexa eventos
public class FecharPedidoHandler {
    public void executar(FecharPedido comando) {
        Pedido pedido = repositorio.carregar(comando.pedidoId());  // replay do stream
        pedido.fechar();                                           // regras e invariantes
        eventStore.acrescentar(
            "pedido-" + comando.pedidoId(),
            pedido.versaoEsperada(),       // concorrência otimista por stream
            pedido.eventosPendentes());    // fatos gerados, e não linhas atualizadas
        // nada de tocar na visão aqui: a projeção cuida disso, de forma assíncrona
    }
}

// Lado de leitura: projetor consome o fluxo global e mantém a visão
public class ResumoPedidoProjecao {
    public void processar(EventoEnvolto envolto) {
        if (envolto.tipo().equals("PedidoPago")) {
            resumoDao.atualizarStatus(envolto.pedidoId(), "PAGO");   // formato da tela
        }
        if (envolto.tipo().equals("PedidoCancelado")) {
            resumoDao.atualizarStatus(envolto.pedidoId(), "CANCELADO");
        }
        checkpoint.gravar(envolto.posicaoGlobal());  // idempotência por posição
    }
}

A sincronização entre os lados herda as regras de consumo de log: entrega at-least-once, idempotência no projetor, ordenação garantida por stream e checkpoints duráveis. A diferença para um CQRS sem Event Sourcing é que a fila de sincronização é o próprio log de eventos, com ordem e significado, e não um canal técnico adicional para operar. Menos infraestrutura no caminho, mais disciplina no consumo.

Graus de adoção da combinação

Assim como o CQRS isolado, a combinação não é um interruptor liga-desliga, é uma rampa de degraus com custo crescente. O erro frequente é começar no degrau mais alto por inspiração de artigo, e o caminho saudável é subir quando a medida de latência, carga ou variedade de consulta justificar.

Degrau Descrição Custo adicional Quando usar
ES sem projeções Leituras apenas por reidratação do agregado Nenhum além do próprio ES Pouca consulta, foco em histórico e integração
Projeções na mesma base Uma ou duas visões em tabelas derivadas do mesmo banco Baixo; projetor único e transação comum Listagens sobre streams longos, começo da separação
Múltiplas visões físicas Read models em tecnologias distintas, sincronizados por eventos Médio; pipeline completo e consistência eventual Escala de leitura e formas de consulta variadas
Serviços separados Escrita e leitura em serviços independentes, escalados à parte Alto; operação distribuída completa Times distintos, escalas distintas, domínio grande

A maioria dos sistemas que precisa da combinação vive nos degraus 2 e 3. O degrau 4 é raro e deve ser precedido de evidência de que a operação distribuída é suportada pelo time, porque o custo de deploys, versionamento e observabilidade duplica nele.

Critérios objetivos de adoção

A combinação cobra caro, e a decisão merece critérios verificáveis, e não entusiasmo.

  • Auditoria e conformidade exigem histórico completo por construção; se o negócio já precisaria manter tabela de log paralela, o Event Sourcing paga a si mesmo.
  • A razão entre leituras e escritas é alta e medida, com formas de consulta variadas que justificam múltiplas visões do mesmo fato.
  • Há necessidade real de reconstruir modelos, seja para migração, seja para responder perguntas novas sobre o passado; o replay é o argumento exclusivo do Event Sourcing.
  • O time tem maturidade em idempotência, versionamento de eventos e observabilidade de defasagem; sem isso, o custo de operação supera o ganho.
  • Consultas e escritas têm perfis de escala incompatíveis em um único modelo, com picos comprovados e não hipotéticos.

Se apenas um critério se aplica, adote só o padrão que o atende. Auditoria sem assimetria de leitura pede Event Sourcing com leituras simples; assimetria de leitura sem exigência de histórico pede CQRS sobre banco convencional, possivelmente com captura de mudanças. A combinação é a resposta quando os dois conjuntos de critérios aparecem juntos no mesmo contexto de domínio.

Quando cada padrão funciona sozinho

CQRS sem Event Sourcing atende bem sistemas cujo problema é a forma da consulta: relatórios que não podem pesar na base transacional, buscas textuais, telas com desnormalização agressiva. A escrita continua relacional, as invariantes ficam protegidas por transações, e as visões nascem de sincronização na mesma transação ou por captura de dados alterados. É o ponto de partida da maioria dos times, e muitos nunca precisam sair dele.

Event Sourcing sem CQRS estruturado é menos comum, mas existe: entidades de baixa consulta, como configurações, contratos e registros fiscais, cujo valor está no histórico e na integração por eventos. A leitura de estado acontece por reidratação no caminho de comando, e as poucas consultas externas consomem os eventos publicados. Nesse arranjo não há um lado de leitura estruturado, e o custo de projeções não é pago. A fronteira entre isso e a combinação é justamente o volume e a variedade de consultas.

Sinais de que o Event Sourcing basta sozinho:

  • A entidade recebe centenas de comandos por dia, mas as consultas de estado são poucas dezenas.
  • O consumidor externo principal é outro sistema, que assina os eventos e mantém a própria visão.
  • O histórico é o produto: auditoria, rastreabilidade e reprodução de decisões passadas.

Sinais de que o CQRS basta sozinho:

  • A assimetria é de consulta, e o histórico detalhado não tem consumidor regulatório nem analítico.
  • As visões podem ser derivadas de tabelas com transformação trivial, sem regra de negócio na montagem.
  • O time não tem maturidade para idempotência, versionamento de eventos e replay, e o prazo não permite construí-la.

Comparação entre os três arranjos

Aspecto CQRS sem ES ES sem CQRS estruturado ES + CQRS
Fonte de verdade Tabelas normalizadas Log de eventos Log de eventos
Sincronização da leitura Transação, gatilho ou CDC Não se aplica; leitura por replay Assinatura de eventos com checkpoint
Consulta de estado atual Direta e imediata Cara, exige reidratação Projeção pronta, eventual
Auditoria histórica Parcial, depende de log extra Completa por construção Completa por construção
Reconstrução de visões Limitada ao dado corrente Possível por replay total Possível por replay total
Complexidade operacional Baixa a média Média Alta
Cenário típico Relatórios sobre CRUD estável Entidades históricas de pouca consulta Alta escala de leitura com histórico rico

A complexidade acumulada dos dois padrões

Combinar é multiplicar superfícies de manutenção, e o time precisa saber o que entra na conta. Do lado do Event Sourcing: versionamento de eventos com upcasting, snapshots, replay em produção, backup do store e política de retenção. Do lado do CQRS: um read model por consulta, defasagem visível para o usuário, contratos de leitura versionados e sincronização monitorada. Juntos, os dois exigem idempotência em toda parte, correlação ponta a ponta e testes que cobrem o caminho completo do comando até a visão.

A acumulação também aparece nas equipes e nos processos. Toda mudança de modelo passa por duas perguntas: quais eventos mudam e quais visões reagem. Cada evento novo é um contrato público; cada visão nova é um consumidor permanente, com checkpoint e operação próprios. Times pequenos tendem a subestimar esse custo acumulado, e a saída madura é começar por um único contexto de domínio com alta assimetria, e não pela arquitetura inteira de uma vez.

O checklist operacional mínimo da combinação resume o que precisa estar de pé antes do primeiro cliente real:

  • Assinatura do fluxo global com reconexão automática e checkpoint persistido por projeção.
  • Idempotência de projetor verificada por teste, usando reentrega de evento como cenário padrão.
  • Métrica de lag por projeção e alerta de crescimento sustentado, com runbook de reprocessamento.
  • Teste de reconstrução completa das visões a partir do log, executado em ambiente isolado com dados mascarados.
  • Política de backup e restauração do store, com ensaio de restauração periódico e documentado.
  • Contratos de leitura versionados, porque o DTO publicado para outras telas e sistemas é interface pública.

Erros comuns e boas práticas

  • Combinar os padrões por padrão em sistemas novos; comece pelo degrau que resolve o problema medido, e suba quando a evidência aparecer.
  • Escrever na visão dentro do handler de comando; a sincronização é papel da projeção, e esse atalho reintroduz a dupla gravação disfarçada.
  • Criar projeções para consultas raras; consultas frias podem ler o store sob demanda, sem visão permanente para manter.
  • Ignorar a defasagem no contrato da API de leitura; a combinação herda toda a consistência eventual do CQRS, com os mesmos padrões de mitigação.
  • Tratar o replay como alternativa à projeção para consultas quentes; replay é ferramenta de reconstrução, e não caminho de consulta.
  • Esquecer de testar a reconstrução completa das visões; o teste de replay ponta a ponta é o seguro da arquitetura.
  • Deixar checkpoints sem observabilidade; a posição global processada por projeção precisa aparecer em painel e alerta.
  • Acoplar o formato do evento ao formato da tela; o evento registra o fato, e a projeção decide a forma da exibição.
  • Subir todos os contextos para a combinação de uma vez; pilote em um domínio, extraia as ferramentas comuns, e depois expanda.
  • Modelar eventos como espelho do banco relacional antigo; desenhe fatos do domínio, e não clones de tabela.

Os sintomas mais frequentes quando a combinação está mal adotada seguem padrão reconhecível.

Sintoma em produção Causa provável Mitigação
Visões defasadas por minutos em picos Projetor pesado competindo com reprocessamento Priorizar eventos, limitar concorrência de replay e medir lag por etapa
Dados duplicados no read model Projetor sem idempotência sob reentrega Deduplicar por posição global e testar reentrega em CI
Time evita mudar o modelo por medo do impacto Acoplamento implícito entre evento e telas Separar fato de exibição na projeção e versionar contratos
Incidente de store sem caminho de recuperação Backup sem ensaio de restauração Rotina periódica de restauração com validação de replay

Práticas que consolidam a combinação: documente, para cada visão, os eventos que a alimentam e a posição do checkpoint; automatize o teste de reconstrução das visões com dados reais mascarados; meça a defasagem ponta a ponta como o número principal do sistema; e revise periodicamente se cada projeção ainda justifica sua existência, desativando as que perderam consumidor. A combinação entrega o melhor dos dois padrões quando cada peça cumpre um papel claro: o log guarda o que aconteceu, as projeções respondem como está, e os comandos decidem o que pode acontecer.