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

02 - Publicar-Assinar (Pub-Sub)

#assinantes#desacoplamento#fanout#pub-sub#topicos
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione 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:

  1. O assinante registra interesse no tópico, criando uma assinatura, que pode ser durável ou efêmera.
  2. O publicador envia a mensagem ao tópico com o broker, sem lista de destinatários no payload.
  3. O broker identifica todas as assinaturas ativas e enfileira uma cópia para cada uma.
  4. 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.