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

Domain Events no Node.js: eventos do domínio

Atualizado em: 14 de outubro de 2026

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

Domain Events no Node.js representam fatos relevantes que já aconteceram dentro do negócio. Exemplos incluem PedidoConfirmado, PagamentoAprovado e ClienteBloqueado.

Eventos ajudam a separar o núcleo de domínio de efeitos colaterais como envio de e-mail, atualização de projeções, auditoria e integração com outros contextos. O evento descreve o fato; handlers decidem o que fazer depois.

Evento não é comando

Comandos expressam intenção: ConfirmarPedido. Eventos registram resultado: PedidoConfirmado. Um comando pode falhar; um evento já aconteceu e não deve ser renomeado como ação futura.

Estrutura de um evento

export function pedidoConfirmado({ pedidoId, clienteId, total, occurredAt }) {
  return Object.freeze({
    id: crypto.randomUUID(),
    type: 'PedidoConfirmado',
    aggregateId: pedidoId,
    version: 1,
    occurredAt,
    data: { clienteId, total }
  });
}

Inclua identificador, tipo, aggregate, versão, data e payload mínimo. Evite copiar todo o aggregate.

Registro dentro da entidade

confirmar({ confirmadoEm }) {
  if (this.status !== 'rascunho') throw new Error('Estado inválido');
  this.status = 'confirmado';
  this.#eventos.push(pedidoConfirmado({
    pedidoId: this.id,
    clienteId: this.clienteId,
    total: this.total.centavos,
    occurredAt: confirmadoEm
  }));
}

removerEventos() {
  return this.#eventos.splice(0);
}

A entidade registra o fato no mesmo ponto em que a regra muda o estado.

Dispatcher em memória

export class EventBus {
  #handlers = new Map();

  on(type, handler) {
    const list = this.#handlers.get(type) ?? [];
    list.push(handler);
    this.#handlers.set(type, list);
  }

  async publish(event) {
    for (const handler of this.#handlers.get(event.type) ?? []) {
      await handler(event);
    }
  }
}

Esse modelo é útil dentro do processo, mas não garante entrega após falhas.

Quando publicar?

Publicar antes do commit pode gerar um efeito externo mesmo se a transação falhar. Publicar apenas depois do commit pode perder o evento se o processo cair. O padrão Outbox resolve essa lacuna persistindo estado e evento na mesma transação.

Handlers

eventBus.on('PedidoConfirmado', async event => {
  await email.enviarConfirmacao(event.data.clienteId);
});

Handlers devem ser idempotentes porque mensagens podem ser entregues novamente.

Eventos internos e integração

Um evento de domínio interno pode conter tipos e detalhes próprios do contexto. Um evento de integração deve ter contrato estável para consumidores externos.

const integrationEvent = {
  type: 'orders.order-confirmed.v1',
  orderId: event.aggregateId,
  totalCents: event.data.total,
  occurredAt: event.occurredAt
};

Use um mapper para evitar acoplar outros sistemas ao modelo interno.

Versionamento

Eventos persistidos ou públicos precisam de versão. Mudanças compatíveis podem adicionar campos opcionais. Mudanças incompatíveis exigem novo tipo ou versão.

Metadados

Metadados úteis incluem:

  • correlationId para acompanhar um fluxo;
  • causationId para indicar o evento ou comando anterior;
  • tenantId em sistemas multi-tenant;
  • traceId para observabilidade;
  • ator responsável pela operação.

Não inclua segredos ou dados pessoais desnecessários.

Ordenação

Ordenação global é cara e muitas vezes desnecessária. Preserve ordem por aggregate ou chave de partição quando a regra depender dela.

Idempotência

async function processar(event) {
  const inserted = await inbox.registrarSeNovo(event.id);
  if (!inserted) return;
  await projeção.aplicar(event);
}

O registro do evento processado e o efeito local devem estar na mesma transação quando possível.

Retries e erros

Falhas transitórias podem ser repetidas com backoff. Erros permanentes devem ir para uma dead letter queue ou estado de intervenção. Não faça retry infinito sem observabilidade.

Testes do domínio

pedido.confirmar({ confirmadoEm: new Date('2026-01-01T10:00:00Z') });
const [event] = pedido.removerEventos();
assert.equal(event.type, 'PedidoConfirmado');
assert.equal(event.aggregateId, pedido.id);

Testes de handlers

Verifique idempotência, payload ausente, retry e efeitos externos. Em integração, confirme que o evento é gravado junto com o aggregate.

Observabilidade

Registre tipo, ID, aggregate, latência, tentativas e resultado. Métricas devem mostrar backlog, falhas e idade da mensagem mais antiga.

Domain Events e Event Sourcing

Usar eventos de domínio não significa usar Event Sourcing. Em um sistema comum, o estado atual continua em tabelas e os eventos coordenam efeitos. No Event Sourcing, eventos são a fonte principal do estado.

Relação com aggregates

A raiz do aggregate é um bom lugar para registrar eventos porque conhece a transição válida. Leia Aggregates no Node.js.

Erros comuns

  • publicar antes do commit;
  • usar eventos como chamadas RPC disfarçadas;
  • payload enorme;
  • sem versionamento;
  • handlers não idempotentes;
  • depender de ordem global;
  • expor modelo interno diretamente;
  • não monitorar backlog.

Conclusão

Domain Events no Node.js tornam fatos do negócio explícitos e desacoplam efeitos secundários. O domínio registra o evento, a camada de aplicação garante persistência e handlers executam reações.

Combine eventos com Outbox, idempotência, contratos versionados e observabilidade. Consulte também Domain-Driven Design no Node.js e Audit Logs no Node.js.

Leia o material de Martin Fowler sobre Domain Event e a documentação de eventos do Node.js.

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