Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Event Sourcing no Node.js: estado baseado em eventos

Atualizado em: 15 de outubro de 2026

Terminal Linux usado em desenvolvimento e administração de aplicações Node.js

Event Sourcing no Node.js armazena a sequência de eventos de um aggregate como fonte principal de verdade. Em vez de salvar apenas o estado atual de um pedido, o sistema grava fatos como PedidoCriado, ItemAdicionado e PedidoConfirmado.

O estado atual é reconstruído aplicando os eventos em ordem. A abordagem oferece histórico completo e capacidade de criar novas projeções, mas aumenta a complexidade de versionamento, snapshots, consistência e operação.

Modelo tradicional e Event Sourcing

No modelo tradicional, uma linha representa o estado mais recente. No Event Sourcing, a linha do tempo é preservada.

[
  { "type": "ContaAberta", "version": 1, "saldo": 0 },
  { "type": "CreditoRecebido", "version": 2, "valor": 5000 },
  { "type": "DebitoRealizado", "version": 3, "valor": 1200 }
]

O saldo atual é derivado dos eventos.

Função de aplicação

export function aplicarEvento(state, event) {
  switch (event.type) {
    case 'ContaAberta':
      return { id: event.aggregateId, saldo: 0, status: 'ativa', version: 1 };
    case 'CreditoRecebido':
      return { ...state, saldo: state.saldo + event.data.valor, version: event.version };
    case 'DebitoRealizado':
      return { ...state, saldo: state.saldo - event.data.valor, version: event.version };
    default:
      return state;
  }
}

Reidratação

export function reidratar(events) {
  return events.reduce(aplicarEvento, null);
}

O aggregate recebe comandos, valida regras e gera novos eventos. Ele não altera diretamente uma tabela de estado.

Event Store

Um event store precisa guardar:

  • ID do evento;
  • ID e tipo do aggregate;
  • versão sequencial;
  • tipo e versão do evento;
  • payload;
  • metadados;
  • data de gravação.
CREATE TABLE events (
  event_id uuid PRIMARY KEY,
  aggregate_id text NOT NULL,
  aggregate_type text NOT NULL,
  aggregate_version integer NOT NULL,
  event_type text NOT NULL,
  payload jsonb NOT NULL,
  metadata jsonb NOT NULL,
  created_at timestamptz NOT NULL,
  UNIQUE (aggregate_id, aggregate_version)
);

Concorrência otimista

A restrição por versão impede duas gravações concorrentes no mesmo aggregate. O comando informa a versão esperada; se outra operação venceu, recarregue e decida se pode repetir.

Eventos são imutáveis

Não edite eventos históricos. Correções devem ser novos eventos ou transformações controladas durante leitura. Alterar o passado quebra auditoria e projeções.

Projeções

Projeções transformam eventos em modelos de leitura.

async function aplicarPedidoConfirmado(event, trx) {
  await trx.query(
    `INSERT INTO pedidos_resumo (id, status, total)
     VALUES ($1, 'confirmado', $2)
     ON CONFLICT (id) DO UPDATE SET status = EXCLUDED.status`,
    [event.aggregateId, event.data.total]
  );
}

Uma projeção pode ser descartada e reconstruída.

Snapshots

Aggregates com milhares de eventos podem usar snapshots periódicos. O snapshot guarda estado e versão; a reidratação aplica apenas eventos posteriores.

Snapshots são otimização, não fonte principal. Eles precisam ser descartáveis.

Versionamento de eventos

Eventos históricos permanecem por anos. Use versões e upcasters para converter payloads antigos durante leitura.

function upcast(event) {
  if (event.type === 'ClienteCadastrado' && event.schemaVersion === 1) {
    return { ...event, schemaVersion: 2, data: { ...event.data, locale: 'pt-BR' } };
  }
  return event;
}

Publicação externa

Eventos internos do store não precisam ser contratos públicos. Um processo pode convertê-los em eventos de integração estáveis.

Idempotência

Projeções e consumidores devem registrar IDs processados. Rebuilds precisam distinguir execução completa de entrega duplicada operacional.

Exclusão e privacidade

Imutabilidade não elimina obrigações de privacidade. Evite dados pessoais em eventos, use referências, criptografia por campo e estratégias de crypto-shredding quando apropriado.

Testes

const history = [contaAberta, creditoRecebido];
const conta = Conta.reidratar(history);
const novos = conta.debitar(1200);
assert.equal(novos[0].type, 'DebitoRealizado');

Testes seguem o formato dado histórico, quando comando, então eventos.

Quando usar

Event Sourcing é útil quando histórico é parte central do produto, decisões precisam ser auditáveis, projeções mudam com frequência ou regras dependem da sequência de acontecimentos.

Quando evitar

CRUDs simples e sistemas sem necessidade de histórico completo raramente justificam o custo. Auditoria pode ser atendida por logs ou tabelas históricas.

Erros comuns

  • usar evento sem versionamento;
  • colocar dados sensíveis no payload;
  • acoplar consumidores ao evento interno;
  • não tratar concorrência;
  • snapshots como fonte definitiva;
  • projeção não idempotente;
  • rebuild sem estratégia operacional;
  • adotar por moda.

Conclusão

Event Sourcing no Node.js preserva a história do domínio e permite reconstruir estado e projeções. O modelo é poderoso, mas exige disciplina com contratos, concorrência, privacidade e operação.

Adote apenas quando o histórico gerar valor real. Para a base conceitual, leia Domain Events no Node.js e Event-Driven Architecture no Node.js.

Consulte a referência de Event Sourcing de Martin Fowler.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita