Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

06 - Event-Driven Architecture (EDA)

#dlq#eda#eventos#idempotência#pub-sub
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione 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.