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.



