Domain Event
Visão Geral
Um evento de domínio é um fato ocorrido, relevante para o negócio, nomeado no
vocabulário do domínio e no passado: PedidoConfirmado, PagamentoRecusado,
CarenciaCumprida.
Ele é o mecanismo que permite coordenar agregados sem violar a regra de um agregado por transação.
Problema
A regra de aggregate diz: modifique um agregado por transação.
Mas casos de uso reais atravessam agregados. Confirmar um pedido precisa reservar estoque, iniciar cobrança e notificar o cliente — três agregados, possivelmente três contextos.
Duas saídas ruins.
Alterar tudo na mesma transação, o que produz agregados grandes e conflitos de concorrência.
Ou colocar a coordenação no serviço de aplicação, que passa a conhecer todos os agregados envolvidos e vira o lugar onde a regra de sequência mora implicitamente.
Eventos de domínio dão a terceira: o agregado registra o que aconteceu, e quem interessa reage — cada um na sua transação.
Conceitos Centrais
O evento é do domínio, não da técnica
Um evento de domínio tem nome que um especialista reconhece. PedidoConfirmado
é evento de domínio; PedidoAtualizado não é — "atualizado" é vocabulário de
banco de dados.
Se o especialista não entende o nome, o evento não captura um fato do negócio.
O agregado registra; a aplicação publica
O padrão que funciona:
class Pedido:
confirmar():
... valida invariantes ...
this.status = CONFIRMADO
this.registrarEvento(new PedidoConfirmado(id, itens, total))
O agregado registra o evento numa lista interna. O serviço de aplicação publica depois de persistir com sucesso.
A ordem importa: publicar antes de persistir produz eventos de coisas que não aconteceram, se a transação falhar.
Evento de domínio versus evento de integração
Distinção que evita o erro mais caro deste padrão.
Evento de domínio é interno ao bounded context. Carrega conceitos do modelo interno, pode mudar livremente, e os consumidores estão no mesmo contexto.
Evento de integração atravessa a fronteira do contexto. É contrato público: versionado, estável, e expresso em termos que fazem sentido fora.
Publicar eventos de domínio diretamente para fora amarra o modelo interno a consumidores externos — e qualquer refatoração passa a quebrá-los.
A prática correta é traduzir: o evento interno dispara a publicação de um evento de integração, com formato próprio.
O problema da publicação transacional
Se a transação grava no banco e a publicação vai para um message broker, os dois não são atômicos. Pode gravar e não publicar, ou publicar e falhar ao gravar.
A solução usual é o padrão outbox: o evento é gravado numa tabela na mesma transação, e um processo separado o publica. Ver sistemas distribuídos.
Ignorar isso produz perda silenciosa de eventos, que é o defeito mais difícil de diagnosticar deste padrão.
Quando Usar
- Vários agregados precisam reagir a um fato.
- A coordenação não precisa ser transacional.
- O fato tem significado para o negócio, não só para o sistema.
- É preciso desacoplar quem faz de quem reage.
- Auditoria de fatos do negócio é necessária.
Quando Não Usar
Quando a reação precisa ser transacional com a origem. Se a reserva de estoque tem que acontecer ou o pedido não vale, isso não é evento — é parte da mesma operação, e a fronteira do agregado provavelmente está errada.
Quando há um consumidor e ele é fixo. Chamada direta é mais simples e mais rastreável.
Para fatos sem significado de negócio. EntidadeSalva não é evento de
domínio.
Sem tratamento de idempotência no consumidor. A entrega será ao menos uma vez.
Sem observabilidade. Ver arquitetura orientada a eventos: o custo do estilo é rastreabilidade.
Alternativas
- Chamada direta ao serviço de domínio — quando há um consumidor.
- Saga — quando a coordenação precisa de compensação e prazo.
- Serviço de aplicação orquestrando — quando o fluxo é crítico e precisa ser auditável num lugar.
- Processo agendado — quando a latência tolerada é alta.
Trade-offs
| Eventos de domínio | Coordenação direta |
|---|---|
| Agregados pequenos e independentes | Agregado grande ou serviço acoplado |
| Novo consumidor não toca a origem | Toca |
| Uma transação por agregado | Transação ampla |
| Consistência eventual | Forte |
| Fluxo não visível num lugar | Explícito |
| Duplicação e ordem a tratar | Semântica simples |
Modos de Falha
Evento publicado sem persistir. A transação falha depois da publicação.
Evento persistido e não publicado. Sem outbox, o processo morre entre os dois.
Consumidor não idempotente. Duplicação vira efeito duplicado.
Evento de domínio vazando para fora. O modelo interno vira contrato público.
Evento com dado insuficiente. O consumidor precisa consultar a origem, o que recria o acoplamento.
Evento com dado demais. Carrega o agregado inteiro; qualquer mudança de modelo quebra consumidores.
Erros Comuns
Nomear com vocabulário técnico.
Publicar antes de persistir.
Não distinguir evento de domínio de evento de integração.
Ignorar o problema da publicação transacional.
Usar eventos para coordenação que precisa ser transacional.
Exemplo Real
Um sistema de seguros publicava ApoliceEmitida diretamente do agregado para o
message broker, consumido por quatro contextos: cobrança, comissionamento,
relatórios e comunicação.
O evento carregava o objeto Apolice inteiro, serializado.
Dois problemas apareceram.
Uma refatoração do agregado — renomear um campo interno e reestruturar as coberturas — quebrou os quatro consumidores simultaneamente. O modelo interno era contrato público sem que ninguém tivesse decidido isso.
E, num incidente de indisponibilidade do broker, 340 apólices foram emitidas sem que o evento fosse publicado. Nenhuma foi cobrada. Descobriu-se três semanas depois, na conciliação contábil.
As duas correções.
O agregado passou a registrar ApoliceEmitida como evento interno, com o
modelo que quiser. O serviço de aplicação traduz para
ApoliceEmitidaV1 — evento de integração com formato versionado, contendo apenas
os campos que os consumidores precisam: número, segurado, vigência, prêmio,
coberturas em formato próprio.
Refatorações internas deixaram de alcançar os consumidores.
E a publicação passou a usar outbox: o evento de integração é gravado na mesma transação da apólice, e um processo o publica com garantia de ao menos uma vez.
A perda silenciosa deixou de ser possível.
Conceitos Relacionados
- Aggregate — quem registra o evento.
- Application Service — quem publica.
- Arquitetura Orientada a Eventos — o estilo em escala de sistema.
- Event Sourcing — quando os eventos são a fonte de verdade.
Exercício Prático
Liste os eventos que seu sistema publica. Para cada um, verifique: o nome está no passado e no vocabulário do domínio? Ele atravessa a fronteira do contexto? Se sim, é versionado?
Depois verifique como a publicação é feita: existe garantia de que o evento é publicado se e somente se a transação for confirmada?
Perguntas de Entrevista
- Qual a diferença entre evento de domínio e evento de integração?
- Por que registrar no agregado e publicar no serviço de aplicação?
- Como garantir que o evento seja publicado apenas se a transação for confirmada?
Para Aprofundar
- Evans, Eric. Domain-Driven Design. Addison-Wesley, 2003.
- Vernon, Vaughn. Implementing Domain-Driven Design. Addison-Wesley, 2013.
- Richardson, Chris. Microservices Patterns. Manning, 2018 — o padrão outbox.