02 - Publicar-Assinar (Pub-Sub)
video: https://youtu.be/... no frontmatter da nota.Publicar-Assinar (Pub-Sub)
O padrão publicar-assinar é a espinha dorsal da comunicação orientada a eventos em sistemas distribuídos. O produtor, chamado publicador, despacha mensagens para um tópico sem conhecer nenhum de seus destinatários; cada assinante com interesse registrado recebe sua própria cópia do fluxo. O acoplamento entre produtor e consumidores cai a zero em tempo de execução: o publicador não sabe quantos assinantes existem, quem eles são nem o que fazem com o conteúdo. Em produção, o padrão sustenta notificação de eventos de domínio, invalidação de cache, atualização de projeções de leitura, integração com parceiros e distribuição de configuração. Para o arquiteto, é o padrão que permite adicionar um novo interessado em um fluxo de eventos sem tocar uma linha do código que o produz, propriedade que define a escalabilidade organizacional de um sistema.
Conceito e elementos do padrão
O padrão se apoia em três papéis. O publicador gera mensagens e as publica em um tópico, um canal nomeado com identidade própria. O broker mantém o tópico, registra assinaturas e faz a distribuição. O assinante declara interesse no tópico e recebe cópias independentes de cada mensagem publicada a partir de então.
A palavra cópia é o coração da semântica: uma mensagem publicada em um tópico com três assinantes gera três entregas independentes. Cada entrega tem ciclo de vida próprio, com ack, retry e falha isolados. Um assinante lento ou quebrado não impede os outros de receber, e a política de retenção de um não interfere na do outro.
Essa entrega independente é o que diferencia pub-sub de uma fila com competição. Na fila, uma mensagem é trabalho que um consumidor executa; no tópico, é um fato que todos os interessados observam. A mensagem do tópico é informação, e informação por natureza tem múltiplos leitores.
O tópico também funciona como contrato público do produtor. Assim como uma API publica endpoints, o serviço publica tópicos com schema documentado, e os assinantes acoplam-se ao contrato, nunca à implementação. Essa fronteira permite trocar a tecnologia do broker, refatorar o produtor por dentro e mudar o deploy sem quebrar nenhum consumidor, desde que o schema evolua com compatibilidade.
O problema que resolve
O problema raiz é o acoplamento de notificação. Sem pub-sub, o serviço que processa o pagamento precisa chamar e-mail, faturamento, fraudes, analytics e recompensas diretamente. Cada novo interesseiro exige alteração no pagador, novo deploy, novo risco, e uma falha em qualquer chamada compromete o fluxo todo.
Com publicar-assinar, o pagador publica pagamento.aprovado no tópico e encerra sua responsabilidade. Quem precisa do evento assina, e novos assinantes entram sem qualquer mudança no publicador. O número de consumidores pode crescer de três para trinta sem que o código produtor mude uma linha, e essa é a propriedade que escala times, não apenas máquinas.
O segundo problema é a inversão de dependência temporal. O publicador não precisa que nenhum assinante esteja disponível no momento da publicação, e os assinantes podem estar offline e consumir depois, conforme a retenção. O sistema como um todo tolera indisponibilidades parciais degradação em vez de falha em cascata.
Funcionamento interno
O fluxo do padrão em um broker típico:
- O assinante registra interesse no tópico, criando uma assinatura, que pode ser durável ou efêmera.
- O publicador envia a mensagem ao tópico com o broker, sem lista de destinatários no payload.
- O broker identifica todas as assinaturas ativas e enfileira uma cópia para cada uma.
- Cada assinante processa e confirma sua cópia de forma independente, com retry e DLQ próprios.
O fanout é o mecanismo de distribuição: um evento entra e N cópias saem. Em RabbitMQ, o exchange fanout faz isso por bindings; em SNS, o tópico entrega para cada protocolo assinado; em Kafka, cada consumer group é um assinante lógico que lê o mesmo log de partições. A implementação varia, a semântica de cópia independente permanece.
Quando a ordem importa, a técnica usual é derivar o tópico em partições por chave de negócio, como pedido_id, garantindo que eventos da mesma entidade sejam processados em sequência por um único consumidor. O tópico continua broadcast para os grupos, e a ordenação fica restrita ao escopo da entidade, que é o único escopo que quase sempre interessa.
Assinaturas duráveis guardam mensagens enquanto o assinante está offline, dentro da janela de retenção. Assinaturas efêmeras existem só enquanto a conexão vive e soltam mensagens quando o consumidor desconecta. A escolha define se um deploy do consumidor perde eventos ou apenas os atrasa, e deve ser explícita no desenho.
# Publicador: publica o fato e não conhece nenhum destinatário
def aprovar_pagamento(pedido):
pagamento = executar_cobranca(pedido)
topico.publicar(
assunto="pagamento.aprovado",
corpo={
"pedido_id": pedido.id,
"valor": pagamento.valor,
"moeda": "BRL",
"ocorrido_em": pagamento.aprovado_em.isoformat(),
},
)
# nenhuma chamada a e-mail, faturamento ou fraudes neste ponto
# Assinantes independentes, cada um com sua própria assinatura e DLQ
def assinar(topico, nome, handler):
topico.assinar(nome=nome, filtro=handler.filtro)
while True:
mensagem = topico.receber(nome)
try:
handler.processar(mensagem.corpo)
mensagem.ack() # confirma apenas a sua cópia
except Exception:
mensagem.nack() # retry isolado dos demais
Entrega independente e tolerância a falha
A independência entre assinantes tem consequências operacionais concretas. O analytics pode cair sem que o e-mail pare de sair, e o faturamento pode acumular backlog sem que a antifraude atrase. Cada assinatura tem sua própria profundidade, seu próprio lag e seus próprios alertas, e a saúde de uma não é a saúde de todas.
O contraponto é o custo do fanout: um evento com cinquenta assinantes gera cinquenta entregas, com o consumo de rede, armazenamento e processamento correspondentes. Em brokers com retenção por assinatura, um assinante morto e esquecido acumula mensagens indefinidamente e vira passivo de custo e de incidente. Governança de assinaturas é parte do padrão, não um extra.
# Exemplo: assinatura durável com política de retry isolada
assinatura:
topico: pagamento.aprovado
nome: faturamento-worker
duravel: true # sobrevive a deploy e restart
retencao: 7d # janela de espera enquanto offline
filtro:
atributo: moeda
valor: BRL # só processa recorte de interesse
retry:
max_tentativas: 5
backoff: exponencial
dlq: faturamento-dlq # destino após esgotar tentativas
Comparação com outros padrões
| Aspecto | Pub-Sub | Fila ponto a ponto | Chamada direta (REST) |
|---|---|---|---|
| Destinatários por mensagem | Todos os assinantes | Exatamente um | Um destinatário conhecido |
| Acoplamento produtor-consumidor | Nenhum, só o tópico | Nenhum, só a fila | Forte, contrato e disponibilidade |
| Novo consumidor | Assina e pronto, zero mudança | Não se aplica, pois é competição | Requer mudança no produtor |
| Disponibilidade do consumidor | Opcional, com assinatura durável | Necessária para drenar backlog | Obrigatória no momento da chamada |
| Entrega por assinante | Independente, com retry próprio | Compartilhada entre competidores | Única, com retry do chamador |
| Custo de escala | Multiplica por assinante | Linear no volume | Linear por chamada |
A escolha entre pub-sub e fila não é exclusiva, e a composição mais comum em produção usa os dois: o tópico faz a distribuição do fato, e cada assinante consome de uma fila ligada à sua assinatura. O padrão fanout do SNS para SQS é exatamente essa composição, e ela combina distribuição flexível com competição e DLQ por consumidor.
Casos de uso consolidados
- Eventos de domínio entre bounded contexts: o pedido publica pedido.criado, e estoque, cobrança e logística assinam cada um com seu recorte.
- Invalidação de cache distribuída: alteração no banco publica a chave alterada, e cada nó de aplicação invalida sua cópia local.
- Projeções e views de leitura: cada mudança alimenta atualização de uma ou mais projeções otimizadas para consulta.
- Integração com parceiros e sistemas legados: assinaturas com filtros entregam só o recorte contratado, sem exposição do fluxo completo.
- Auditoria e analytics: um assinante dedicado observa tudo que acontece, sem instrumentar produtores um a um.
Em produção real, um tópico de pagamento aprovado costuma alimentar seis a dez assinaturas simultâneas, entre sistemas internos, parceiros e instrumentação. É o multiplicador silencioso do padrão: cada integração nova custa apenas o trabalho do assinante novo, e o custo marginal do produtor é zero. É essa economia que faz o pub-sub valer mais que qualquer benchmark de throughput.
Erros comuns
- Presumir ordenação global: a maioria dos tópicos não garante ordem entre assinaturas, e lógicas sensíveis a sequência precisam de chave de partição ou versionamento no evento.
- Publicar comandos em vez de fatos: um tópico com mensagem imperativa cria acoplamento semântico, pois o publicador passa a ditar o que cada assinante deve fazer.
- Assinatura sem DLQ: falhas permanentes no assinante ficam em loop de retry infinito, consumindo o fluxo e escondendo o problema dos demais.
- Payload anêmico sem chaves de negócio: assinantes que precisam consultar o produtor para cada evento recriam o acoplamento que o padrão removeu.
- Tópicos com nomes sem taxonomia: pagamento_aprovado, PaymentApproved e pagamentos.aprovados no mesmo sistema tornam a descoberta e a governança inviáveis.
Boas práticas
- Versione o schema do evento desde a primeira publicação, com política de compatibilidade retroativa, para permitir deploys independentes de assinantes.
- Prefira fatos passados e verificáveis no nome do tópico, como pedido.cancelado, e reserve o tempo verbal presente para comandos internos.
- Defina DLQ e política de retry por assinatura, com alerta quando a DLQ crescer, evitando que um assinante quebrado passe despercebido.
- Use filtros de assinatura para entregar recortes, reduzindo custo de fanout e processamento descartado no consumidor.
- Monitore lag por assinatura, e não apenas throughput do tópico, porque a experiência de cada consumidor é o que afeta o negócio.
- Documente o catálogo de tópicos com produtor, significado dos atributos e dono, transformando o padrão em infraestrutura de descoberta, não só de transporte.