06 - Event-Driven Architecture (EDA)
video: https://youtu.be/... no frontmatter da nota.Event-Driven Architecture (EDA)
A arquitetura orientada a eventos organiza o sistema em torno da publicação e do consumo de eventos: fatos imutáveis que aconteceram, difundidos por um intermediário de mensagens, sem que produtores conheçam consumidores. O produtor declara que algo ocorreu e segue a vida; quem tiver interesse assina e reage no próprio ritmo. Esse desenho elimina acoplamento temporal, o requisito silencioso de que o serviço que chama esteja no ar no mesmo instante que o serviço chamado, e é a base natural de fluxos de negócio em que vários interessados reagem ao mesmo fato. Em produção, EDA é o que permite checkout continuar funcionando com o CRM em manutenção, e é também o estilo que mais rapidamente vira spaghetti de tópicos sem disciplina de contratos, idempotência e observabilidade.
Eventos como Cidadãos de Primeira Classe
Um evento é uma declaração de fato ocorrido, nomeada no passado, imutável depois de publicada e versionada como contrato. Distinguir o que é evento de comando mantém o desenho honesto: comando expressa intenção de quem chama, como ReservarEstoque, e exige que o destino exista; evento declara realidade, como PedidoCriado, e ninguém é obrigado a reagir. O payload varia entre duas escolas com trade-offs claros: notificação mínima, com apenas identificadores, força consumidores a consultarem o produtor para detalhes e recria acoplamento; transferência de estado no evento, com os dados necessários ao consumo, reduz conversas extras e aumenta o payload e a responsabilidade do produtor.
A família de mensagens que circula por uma EDA bem definida:
- Notificação de fato: mensagem mínima que informa e não pretende transportar estado; quem precisa de detalhe conversa depois com o dono.
- Fato com estado embutido: evento que carrega os dados do consumidor, reduzindo chamadas de volta e permitindo visões locais.
- Evento de integração: contrato público entre domínios, com esquema versionado e compatibilidade retroativa garantida em CI.
- Evento de domínio interno: ocorre dentro de um contexto delimitado e alimenta regras como Event Sourcing quando há demanda de histórico.
Boa parte da maturidade de uma EDA mora na gestão desses contratos: esquema versionado, registro central ou arquivo versionado no repositório, política de compatibilidade retroativa e comunicação de depreciação. Mudar um evento sem governança quebra consumidores em silêncio, e o incidente só aparece dias depois, quando o relatório mensal não fecha.
Pub-Sub versus Filas
O intermediário escolhido define as propriedades de entrega. Fila é ponto a ponto: cada mensagem é processada por um consumidor, o que serve para distribuir trabalho entre instâncias competidoras e absorver picos com buffer. Pub-sub é difusão: cada assinante do tópico recebe a própria cópia, o que serve para integrar interessados independentes ao mesmo fato. Kafka combina os dois modelos por cima de um log: tópicos particionados, grupos de consumidor que dividem as partições, e offset controlado por consumidor, o que permite replay do histórico e reprocessamento consciente, propriedade que filas tradicionais não oferecem.
Ordenação merece atenção especial nesse cenário: garantias de ordem valem por partição, não por tópico. Eventos do mesmo cliente ou do mesmo pedido devem rotear pela mesma partição, por chave, para preservar sequência; sem isso, um consumidor pode processar cancelamento antes da criação do pedido. Quando a ordem global importa de verdade, o desenho precisa assumir serialização no produtor ou aceitar a complexidade de coordenar fora do broker.
| Modelo | Entrega | Propriedade marcante | Uso típico |
|---|---|---|---|
| Fila | Um consumidor por mensagem | Concorrência com reconhecimento individual | Distribuir trabalho e absorver pico |
| Pub-sub | Cópia para cada assinante | Novo interessado entra sem tocar no produtor | Notificar vários domínios do mesmo fato |
| Log particionado | Cópia por grupo de consumidor | Replay por offset e ordenação por partição | Integração de alto volume e reprocessamento |
| Stream com janela | Grupo por partição, com estado | Agregação em tempo próximo | Métricas e detecção de padrão |
Coreografia versus Orquestração
Em coreografia, cada serviço reage a eventos e publica novos fatos, e o fluxo emerge da soma das reações, sem cérebro central. Em orquestração, um coordenador conhece os passos do processo, mantém o estado do fluxo e decide o próximo chamado, como fazem Step Functions, Temporal e Camunda. Coreografia maximiza desacoplamento e barato de evoluir, e cobra caro em legibilidade: o fluxo completo não está em lugar nenhum, está espalhado nos assinantes. Orquestração dá visibilidade, retentativa central e auditoria, e cria um componente com visão de todo o processo, que precisa não virar serviço de negócio disfarçado. Em sistemas reais, a combinação é comum: orquestração para o fluxo principal do negócio, coreografia para notificações e integrações satélites.
# Fluxo coreografado de pedido: cada passo reage e publica o próprio fato
passos:
- servico-pedidos publica PedidoCriado
- servico-estoque consome e publica EstoqueReservado
- servico-pagamentos consome e publica PagamentoAprovado
- servico-pedidos consome os dois e marca pedido CONFIRMADO
falhas:
- servico-pagamentos publica PagamentoRecusado
- servico-estoque consome e publica EstoqueLiberado # compensação
observacao: sem traço distribuído, este desenho só se explica no código
Outbox: Publicação Confiável pelo Produtor
A falha clássica do estilo é o produtor gravar o dado no banco e falhar antes de publicar o evento, ou publicar e falhar antes de gravar, deixando o ecossistema acreditando em um fato que não aconteceu. O padrão outbox elimina a janela: o evento é gravado como linha na tabela de saída, na mesma transação da mudança de negócio, e um despachante lê essa tabela e publica no broker, marcando o envio. Com captura de mudança de dados, o CDC assume o papel do despachante sem adicionar código à aplicação. O resultado é a garantia de que todo fato gravado circula, e a fila de saída vira objeto de monitoramento, com alerta quando eventos ficam pendentes além do tempo acordado.
# Tabela outbox escrita na mesma transação do INSERT do pedido
outbox:
id: "8f14e45f" # identificador do evento publicado depois
agregado: pedido # origem do fato
evento: PedidoCriado
payload: "<json do pedido>" # capturado no momento da transação
status: pendente # pendente, publicado, com tentativas
criado_em: 2025-03-14T12:03:11Z
despachante:
estrategia: CDC pela changefeed da tabela, sem consulta periódica
ordem: por agregado, preservando sequência de eventos do mesmo pedido
Eliminação de Acoplamento Temporal
O benefício estrutural da EDA é o produtor não precisar de nenhum consumidor online para concluir seu trabalho. O checkout publica PedidoCriado e responde ao cliente; o estoque, o pagamento e o e-mail processam quando puderem, do próprio ritmo, com o broker segurando a fila entre eles. Isso converte picos em atraso controlado em vez de erro: uma sobrecarga do CRM não derruba vendas, apenas atrasa a sincronização. O mesmo desacoplamento vale para implantação: consumidores podem estar em deploy enquanto o produtor publica, porque a mensagem está no intermediário, não na memória de um processo. A contrapartida é a inversão da pressão operacional: o gargalo deixa de ser CPU e vira profundidade de fila, que precisa ser monitorada com a mesma seriedade que latência de API.
Consistência Eventual e Idempotência
Infraestrutura de mensageria industrial promete at-least-once, e isso significa que duplicatas vão acontecer: retentativa após timeout, rebalanceamento de partição e reentrega de lote parcial duplicam efeito se o consumidor não se proteger. Consumidor idempotente é, portanto, requisito de desenho, e não refinamento: a chave de idempotência, normalmente o identificador do evento, é verificada em um armazenamento deduplicado na mesma transação do efeito, garantindo que processar duas vezes produza o mesmo resultado que processar uma.
# Consumidor idempotente: aplica o efeito exatamente uma vez por evento
def processar(evento, repositorio):
chave = evento["id"] # identificador único do evento
if repositorio.ja_processado(chave): # consulta na mesma transação
return "duplicado_ignorado" # at-least-once entrega repetidos
repositorio.aplicar_efeito(evento) # reservar estoque, emitir nota
repositorio.marcar_processado(chave) # grava junto, atomicamente
A consistência eventual também exige decisões de produto: a tela que mostra confirmação em andamento, a leitura que aceita stale temporário, o relatório que concilia à noite. Padronizar o outbox no lado produtor, publicando o evento na mesma transação do banco, fecha a janela em que o fato aconteceu e o evento se perdeu. Sem isso, o sistema tem a pior combinação possível: dados distribuídos e publicação melhor esforço.
Dead Letter Queue e Tratamento de Falhas
Falha de consumo precisa de política explícita, porque mensagem não vai embora sozinha. Erros transitórios, instabilidade de rede e timeout de banco, pedem retentativa com backoff; erros permanentes, payload inválido e regra de negócio reprovada, pedem destino imediato para análise. Após o máximo de tentativas, a mensagem vai para a dead letter queue, com o erro original anexado, e a fila funciona como sala de emergência: precisa de alerta de profundidade, runbook de reprocessamento e higiene, porque DLQ esquecida é cliente esperando algo que nunca vai acontecer. Distinguir falha de mensagem de falha de infraestrutura evita o pior erro operacional: reprocessar em loop um lote inteiro que só tinha uma mensagem venenosa.
Política mínima de tratamento de falhas por consumidor:
- Classificar o erro antes de reagir: transitório reagenda; permanente vai direto para a DLQ com causa registrada no cabeçalho.
- Limitar retentativas com backoff: três a cinco tentativas espaçadas evitam martelar um serviço que já está em queda.
- Anexar contexto na DLQ: payload original, cabeçalhos, rastro e mensagem de erro, para que o diagnóstico não dependa de forense.
- Alertar por profundidade e por idade: fila com itens parados há horas é incidente, mesmo que a contagem pareça pequena.
- Rotina de redrive testada: reprocessar em ambiente de teste antes de devolver mensagens para produção, especialmente em lotes.
Casos de Uso Reais em Produção
Fluxos que se beneficiam de EDA em produção real:
- Checkout e cumprimento de pedido: venda publicada uma vez, com estoque, pagamento, faturamento e logística reagindo em paralelo e sem cadeia síncrona.
- Atualização de visões de leitura: dados denormalizados de busca e telas alimentados por eventos, mantendo leitura rápida e writes simples.
- Integração com parceiros e sistemas legados: fato interno publicado, e adaptadores traduzem para cada protocolo externo sem acoplar o núcleo.
- Notificação multicanal: e-mail, push e SMS consumindo o mesmo evento, com cadência e filtro próprios de cada canal.
- Detecção de fraude e analítica em tempo próximo: stream processado com janelas enquanto o dado flui, sem consulta pesada no caminho transacional.
O ponto em comum é a presença de vários interessados independentes no mesmo fato, com tolerância a atraso de milissegundos a segundos. Onde a reação precisa ser imediata e singular, como debitar a conta no saque, EDA não é o mecanismo certo; é a camada de difusão ao redor dele.
Erros Comuns e Boas Práticas
| Sintoma em produção | Causa provável | Mitigação |
|---|---|---|
| Registros duplicados após pico | Consumidor sem idempotência | Chave de idempotência com deduplicação |
| Eventos que nunca chegaram | Publicação fora da transação local | Outbox com captura via CDC |
| Fila cresce sem convergir | Consumidor mais lento que o produtor | Escalar consumidores e particionar carga |
| DLQ com milhares de itens e ninguém viu | Sem alerta nem rotina de redrive | Alerta de profundidade e runbook |
| Fluxo impossível de depurar | Coreografia sem rastro distribuído | Tracing por evento e fluxos-chave orquestrados |
| Consumidor quebrado por publicação nova | Contrato de evento sem compatibilidade | Esquema versionado e checagem em CI |
Boas práticas que sustentam EDA em produção:
- Nomeie eventos no passado e trate como contrato: versão, compatibilidade e depreciação comunicada, com checagem automática no pipeline.
- Deduplicação padrão em todo consumidor: o template de serviço já nasce com armazenamento de chaves e medição de reentregas.
- Monitore lag e profundidade de fila como SLO: atraso de processamento é o sintoma novo deste estilo, e precisa de limite acordado.
- Mantenha payload com intenção clara: transferência de estado para quem precisa consumir sem conversa, notificação para fatos leves, e a escolha documentada.
- Orquestre o fluxo de receita, coreografe o resto: o caminho crítico de negócio merece visibilidade central; integrações satélites seguem por eventos.
- Exercite o redrive em ambiente de teste: reprocessamento nunca deve ser descoberto pela primeira vez durante um incidente.
- Separe tópico de comando: tópico difunde fatos; ordem a um serviço específico vai por fila ou API, mantendo o vocabulário honesto.