Marcio Cunha

Modelagem de Domínios Complexos com Event Sourcing e Projeções Read-Side Otimizadas

Descubra como construir sistemas resilientes e escaláveis combinando Event Sourcing e projeções Read-Side otimizadas para lidar com regras de negócio complexas na prática.

Marcio Cunha•5 min
Também disponível em:EnglishEspañol
Resumo
  • O armazenamento imutável de eventos preserva cada mudança de estado sem perda de histórico analítico ou de auditoria.
  • A separação estrita entre escrita e leitura elimina gargalos tradicionais de concorrência em bancos de dados relacionais.
  • As projeções adaptadas aceleram consultas complexas ao pré-computar visões prontas para consumo na interface do usuário.
  • A eventual consistency exige que equipes projetem fluxos tolerantes a pequenas janelas de latência na sincronização de dados.
  • A complexidade operacional inicial compensa largamente com ganho de rastreabilidade e flexibilidade evolutiva a longo prazo.

Entendendo o Problema dos Modelos de Dados Tradicionais

Na engenharia de software convencional, costumamos salvar o estado atual de um registro sobrescrevendo dados em tabelas relacionais. Na prática, isso significa que, quando um usuário altera o endereço de entrega, o endereço anterior desaparece para sempre. Esse modelo simplificado funciona bem em aplicações simples, mas falha miseravelmente quando precisamos auditar o passado, entender o comportamento do usuário ao longo do tempo ou explicar por que uma decisão financeira foi tomada em um segundo específico. O custo dessa perda de contexto histórico cresce exponencialmente à medida que o negócio evolui e as regras de conformidade se tornam mais rígidas.

Para resolver esse dilema de perda de informação, precisamos mudar nossa perspectiva fundamental sobre o tempo e a persistência. Em vez de registrar apenas o retrato estático do momento, passamos a registrar cada fato ocorrido como uma verdade inalterável. Esse conceito, conhecido como persistência baseada em eventos, transforma a base de dados em um diário de bordo cronológico e imutável. Na prática, a aplicação deixa de perguntar qual é o estado atual da tabela e passa a somar ou interpretar a sequência de acontecimentos que levaram até ali, garantindo rastreabilidade total de cada transação de negócio.

O Conceito e o Funcionamento do Event Sourcing

O Event Sourcing é um padrão arquitetural onde o estado de uma aplicação é determinado por uma sequência imutável de eventos de negócio. Na prática, cada ação importante realizada por um usuário ou sistema — como 'CarrinhoCriado', 'PagamentoAprovado' ou 'PedidoEnviado' — gera um objeto de evento estruturado que é armazenado de forma append-only, ou seja, sem permitir edições ou exclusões. Essa abordagem garante que o sistema possua uma trilha de auditoria nativa e inquebrável, ideal para domínios complexos onde o histórico importa tanto quanto o presente.

Quando precisamos calcular o estado atual de um objeto de domínio, como o saldo de uma conta bancária ou os itens de um pedido, o sistema lê todos os eventos passados daquele agregado e os aplica sequencialmente em memória. Na prática, isso significa que o estado é sempre derivado e nunca armazenado diretamente como uma linha estática em uma tabela comum. Para evitar gargalos de performance quando a lista de eventos se torna muito longa, utilizamos o conceito de snapshots, que consistem em salvar pontos de controle periódicos do estado acumulado para acelerar a inicialização do agregado.

{
  "eventId": "evt_987654321",
  "aggregateId": "order_123",
  "eventType": "OrderPlaced",
  "timestamp": "2026-03-31T10:00:00Z",
  "data": {
    "customerId": "cust_456",
    "items": [
      {"productId": "prod_789", "quantity": 2, "price": 49.90}
    ]
  }
}

Desafios de Consulta e a Necessidade de Projeções Read-Side

Um dos maiores desafios práticos do Event Sourcing é responder a consultas complexas ou paginações rápidas. Se precisarmos buscar todos os pedidos de um cliente cujos totais ultrapassem determinado valor em um período específico, recalcular isso a partir do fluxo bruto de eventos seria computacionalmente inviável em tempo de execução. Na prática, ler milhares de eventos para cada requisição HTTP destruiria a performance da aplicação e sobrecarregaria a infraestrutura de rede e memória.

É exatamente aqui que entram as projeções Read-Side, que funcionam como ouvintes dedicados a traduzir o fluxo contínuo de eventos em tabelas ou documentos otimizados exclusivamente para leitura. Na prática, cada vez que um evento como 'OrderPlaced' é disparado, um projetor assíncrono consome esse evento e atualiza uma base de dados relacional ou NoSQL desnormalizada, projetada sob medida para as telas da aplicação. Dessa forma, a escrita prioriza a segurança e a integridade temporal, enquanto a leitura entrega respostas instantâneas sem esforço computacional redundante.

Implementação Prática de um Projetor Assíncrono

Construir um projetor eficiente exige entender como escutar o barramento de eventos e atualizar a base de leitura de forma idempotente, garantindo que a mesma mensagem processada duas vezes não corrompa o estado final. Na prática, utilizamos identificadores únicos de eventos ou versões de controle para ignorar mensagens duplicadas que possam chegar devido a falhas de rede e retransmissões automáticas. Esse cuidado operacional blinda o sistema contra inconsistências visuais na interface do usuário.

O código abaixo ilustra a lógica fundamental de um projetor em Node.js que escuta eventos de pedidos e atualiza um repositório otimizado para consultas rápidas na camada de leitura:

async function handleOrderPlacedEvent(event) {
  const existingRecord = await readRepository.findById(event.aggregateId);
  if (existingRecord) return; // Garante idempotência

  const totalAmount = event.data.items.reduce((sum, item) => sum + (item.quantity * item.price), 0);

  const readModel = {
    id: event.aggregateId,
    customerId: event.data.customerId,
    total: totalAmount,
    status: 'PLACED',
    createdAt: event.timestamp
  };

  await readRepository.save(readModel);
}

Consistência Eventual e Trade-offs Arquiteturais

Ao desacoplar a escrita (Event Sourcing) da leitura (Read-Side Projections), adotamos o modelo de consistência eventual. Na prática, isso significa que, imediatamente após o usuário confirmar uma compra, a tela de histórico pode demorar frações de segundo para exibir o novo pedido, pois o evento ainda precisa ser processado pelo projetor assíncrono. Para a maioria das aplicações corporativas e de comércio eletrônico, essa janela imperceptível é um preço irrelevante diante dos imensos ganhos de escalabilidade e resiliência obtidos.

Outro trade-off importante reside na complexidade de versionamento de esquemas de eventos ao longo dos anos. Como os eventos são imutáveis, se a estrutura de dados de um evento antigo precisar mudar devido a exigências regulatórias ou de negócio, precisamos implementar estratégias de upcasting ou tradutores em tempo de leitura. Avaliar cuidadosamente esses custos de manutenção antes de adotar a arquitetura evita retrabalhos severos em equipes de engenharia em estágios avançados do produto.

Considerações Finais

A adoção combinada de Event Sourcing e projeções Read-Side otimizadas representa um salto qualitativo significativo na engenharia de domínios complexos. Embora exija uma mudança profunda de mentalidade na modelagem de dados e traga desafios operacionais inerentes à distribuição assíncrona, os benefícios em termos de auditoria, resiliência e performance de leitura justificam amplamente o esforço. Dominar esses padrões capacita arquitetos e desenvolvedores a construírem sistemas capazes de crescer de forma sustentável e previsível.

O sucesso na jornada rumo a arquiteturas orientadas a eventos depende menos de modismos tecnológicos e mais de um alinhamento rigoroso entre as necessidades reais do negócio e as garantias de consistência técnica escolhidas. Ao planejar cuidadosamente cada projetor e manter o modelo de domínio isolado de preocupações de infraestrutura, sua equipe garantirá um software robusto, flexível e preparado para os desafios futuros.