DevGuideTK

Publicação e assinatura

Um evento, vários consumidores independentes, e o desacoplamento que isso traz.

Um fato, vários interessados

Quando um pedido é criado, vários serviços precisam saber: o estoque reserva os itens, o serviço de e-mail envia a confirmação, a área de análise registra a venda e o programa de fidelidade soma pontos.

O serviço de pedidos poderia chamar cada um. Mas aí ele precisa conhecer todos, e cada reação nova (um serviço antifraude, por exemplo) exige mudar o código dele e publicar uma versão nova.

Na publicação e assinatura (publish/subscribe, ou pub/sub), o serviço de pedidos só anuncia o fato: "o pedido 981 foi criado". Ele publica num tópico e não sabe quem está ouvindo. Cada serviço interessado cria uma assinatura (subscription) nesse tópico e recebe a sua própria cópia de cada evento.

                                 ┌─▶ assinatura "estoque"    ─▶ serviço de estoque
serviço de pedidos ─▶ tópico ────┼─▶ assinatura "email"      ─▶ serviço de e-mail
   (publicador)     "pedidos"    └─▶ assinatura "analise"    ─▶ serviço de análise

O serviço antifraude, quando chegar, cria a assinatura dele. O serviço de pedidos não muda.

Ponto a ponto e publicação e assinatura

O tópico anterior tratou da fila de tarefas, em que cada mensagem vai para um consumidor. Publicação e assinatura é o caso em que cada mensagem vai para todos os interessados.

Ponto a ponto (fila)Publicação e assinatura
Cada mensagem é entregue aUm consumidorCada assinatura
O publicador conhece o destinoSim, publica na fila daquele trabalhoNão, publica no tópico
A mensagem costuma serUm comando: "envie este e-mail"Um evento: "o pedido foi criado"
Acrescentar um consumidor novoDivide o trabalho com os atuaisRecebe tudo, sem afetar os outros

A diferença entre comando e evento ajuda a escolher. Um comando tem um dono, que vai executá-lo, e pode falhar ("não consegui enviar"). Um evento descreve algo que já aconteceu; ninguém pode recusá-lo, cada assinante só decide o que fazer com ele. Por isso eventos são nomeados no passado: pedido.criado, pagamento.aprovado.

Assinatura e instância não são a mesma coisa

Este é o ponto que mais confunde. O serviço de estoque roda em três instâncias, para aguentar a carga. Quando o evento "pedido 981 criado" chega, o estoque precisa ser baixado uma vez, não três.

Por isso, quase todo sistema de pub/sub combina os dois modelos:

  • Entre assinaturas, cada uma recebe todas as mensagens.
  • Dentro de uma assinatura, as instâncias do mesmo serviço dividem as mensagens, como numa fila de tarefas. São os consumidores concorrentes (competing consumers).
                          ┌─▶ assinatura "estoque" ─┬─▶ estoque, instância 1
tópico "pedidos" ─────────┤                         ├─▶ estoque, instância 2
                          │                         └─▶ estoque, instância 3
                          └─▶ assinatura "email"   ───▶ e-mail, instância 1

A regra prática: uma assinatura por serviço, não por instância. Cada sistema dá um nome diferente para a assinatura:

SistemaO que faz o papel da assinatura
RabbitMQUma fila ligada à troca (exchange) do tópico
KafkaUm grupo de consumidores (group.id)
Amazon SNSUma fila SQS inscrita no tópico
Google Pub/SubA assinatura (subscription)
Azure Service BusA assinatura do tópico

Errar aqui produz dois defeitos opostos. Se cada instância tem a sua assinatura, o evento é processado três vezes. Se dois serviços diferentes usam a mesma assinatura, eles dividem os eventos entre si, e cada um vê só uma parte.

Durável ou efêmera

Uma assinatura durável guarda as mensagens enquanto o assinante está fora do ar. Quando o serviço de e-mail volta de uma manutenção, os eventos da última hora estão esperando por ele. É o comportamento de uma fila durável no RabbitMQ, de um grupo de consumidores no Kafka e das assinaturas dos serviços gerenciados.

Uma assinatura efêmera só existe enquanto o assinante está conectado. Quem não está ouvindo no momento da publicação perde a mensagem, para sempre. É o caso do Redis Pub/Sub e de filas exclusivas e temporárias no RabbitMQ.

DurávelEfêmera
Assinante fora do arAs mensagens esperamAs mensagens se perdem
Assinante lentoA fila cresceRecebe o que der; no Redis, é desconectado quando o buffer de saída estoura
Bom paraRegras de negócio: estoque, cobrança, e-mailAvisos que perdem valor rápido: atualizar telas abertas, invalidar caches locais

Quem chega depois

Uma assinatura só recebe o que for publicado depois de ela existir. No RabbitMQ, uma mensagem publicada numa troca sem nenhuma fila ligada é descartada, sem erro. No SNS e no Google Pub/Sub, a assinatura nova começa vazia.

O Kafka é a exceção, porque guarda o log: um grupo de consumidores novo pode começar do início do tópico (auto.offset.reset=earliest) e ler todo o histórico retido. O padrão, porém, é latest, que começa pelas mensagens novas.

Isso tem duas consequências práticas:

  • A ordem de implantação importa. Se o publicador sobe antes de a assinatura ser criada, os primeiros eventos se perdem. Crie as assinaturas antes, na infraestrutura, e não só quando o consumidor inicia.
  • Um serviço novo precisa de uma carga inicial. Se ele precisa conhecer os pedidos antigos, eles vêm de outra fonte (uma consulta à API, uma exportação), a menos que o histórico esteja num log como o Kafka.

Filtrar o que cada um recebe

Nem todo assinante quer todos os eventos. O serviço de fidelidade só se interessa por pedidos pagos; o de logística, só pelos do Brasil. Os intermediários permitem filtrar na assinatura, para que a mensagem nem chegue ao assinante:

SistemaComo filtrar
RabbitMQTroca do tipo topic, com padrões na chave de roteamento: pedido.* (uma palavra) ou pedido.# (zero ou mais)
Amazon SNSPolítica de filtro sobre os atributos ou o corpo da mensagem
Google Pub/SubFiltro sobre os atributos, definido na criação da assinatura
Azure Service BusFiltros SQL ou de correlação na assinatura
KafkaNão filtra no intermediário; o consumidor lê e descarta, ou se separam os eventos em tópicos diferentes

O que se ganha e o que se paga

GanhoCusto
O publicador não conhece os assinantesNinguém sabe, olhando o publicador, o que acontece depois de um evento
Reações novas sem mudar o publicadorO formato do evento vira um contrato com assinantes que você não vê
Cada assinante falha e escala sozinhoCada um fica consistente no seu tempo; os dados divergem por um intervalo
Um assinante lento não atrasa os outrosCada assinatura precisa de monitoramento próprio

O primeiro custo é o mais traiçoeiro. Numa chamada direta, o código mostra a sequência. Com eventos, a sequência está espalhada entre serviços, e entender "por que o cliente não recebeu o e-mail" exige olhar cada assinatura. Rastreamento distribuído e um catálogo de quem assina o quê ajudam.

O evento é um contrato

Quando o serviço de pedidos muda o formato do evento, ele pode quebrar assinantes que nem sabe que existem. Mudanças seguras e inseguras:

MudançaAssinantes antigos
Acrescentar um campo opcionalContinuam funcionando, se ignorarem campos desconhecidos
Remover ou renomear um campoQuebram
Mudar o tipo de um campo (número para texto)Quebram
Mudar o significado de um campo, mantendo o nomeContinuam rodando, com resultados errados

A regra é a mesma do versionamento de APIs: só acrescentar. Quando uma mudança incompatível é inevitável, publica-se um evento novo (pedido.criado.v2, ou um campo versao) ao lado do antigo, até todos os assinantes migrarem.

Do lado do assinante, vale o princípio do leitor tolerante (tolerant reader): ler só os campos de que precisa e ignorar o resto.

Por que isso importa quando a IA escreve o código

Um assistente monta um publicador e um assinante em minutos, e o exemplo funciona na primeira execução. Os defeitos aparecem quando o serviço ganha réplicas, reinicia ou evolui:

  • O assinante copia o exemplo do tutorial do RabbitMQ, com uma fila exclusiva de nome gerado pelo servidor. Com três instâncias, cada evento é processado três vezes; quando uma instância reinicia, a fila dela some com os eventos pendentes.
  • No Kafka, o group.id leva o nome da máquina ou um valor aleatório, e cada instância vira um grupo que recebe tudo. Ou o contrário: dois serviços copiam o mesmo group.id e dividem os eventos sem perceber.
  • O Redis Pub/Sub é usado para eventos de negócio, e tudo o que é publicado durante uma reinicialização do assinante se perde.
  • A assinatura é criada pelo próprio consumidor ao iniciar, e os eventos publicados antes da primeira implantação dele não chegam.
  • O assinante falha ao encontrar um campo desconhecido, e uma mudança compatível no publicador derruba o consumo.
  • O publicador manda comandos disfarçados de evento ("enviar-email"), e o desacoplamento some: ele continua decidindo o que os outros fazem.

O próximo nível compara os intermediários para publicação e assinatura, o conteúdo dos eventos e como evoluir o formato sem quebrar ninguém.

Prompts para aprofundar

Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.

  • Publicação e assinatura em cinco minutos

    Explique em no máximo cinco minutos de leitura o que é publicação e assinatura (pub/sub) no backend, para alguém que já entende o básico de filas de mensagens.
    
    Cubra só o essencial: a diferença entre uma fila ponto a ponto e um tópico com assinaturas, a diferença entre evento e comando, por que as instâncias de um mesmo serviço dividem uma assinatura, e a diferença entre assinatura durável e efêmera.
    
    Use o exemplo de uma loja em que estoque, e-mail e análise reagem ao evento "pedido criado". No final, me faça três perguntas curtas para eu verificar se entendi, e corrija as minhas respostas.
  • Assinatura, serviço e instância

    Quero entender de vez a relação entre tópico, assinatura, serviço e instância em publicação e assinatura.
    
    Aja como um mentor de backend. Pergunte primeiro qual é a minha experiência e qual intermediário de mensagens eu conheço melhor.
    
    Use o exemplo de um tópico "pedidos" com dois serviços assinantes, estoque (três instâncias) e e-mail (uma instância). Conduza assim, uma etapa por vez:
    1. Pergunte quantas vezes o evento "pedido 981 criado" deve ser processado por cada serviço.
    2. Mostre o que acontece se cada instância tiver a sua própria assinatura.
    3. Mostre o que acontece se estoque e e-mail compartilharem a mesma assinatura.
    4. Mostre o desenho correto e explique os consumidores concorrentes dentro de uma assinatura.
    5. Traduza o desenho para o RabbitMQ (troca e filas), o Kafka (grupos de consumidores) e um serviço gerenciado (SNS com SQS ou Google Pub/Sub).
    
    Espere a minha resposta em cada etapa antes de continuar.
  • Durável, efêmera e quem chega depois

    Quero entender o que acontece com os eventos quando um assinante está fora do ar, e quando uma assinatura é criada depois que os eventos foram publicados.
    
    Explique:
    1. A diferença entre assinatura durável e efêmera, com o Redis Pub/Sub e uma fila durável do RabbitMQ como exemplos.
    2. O que acontece com um assinante lento em cada caso.
    3. Por que, no RabbitMQ e na maioria dos serviços gerenciados, uma assinatura nova não recebe os eventos anteriores a ela.
    4. Por que o Kafka é diferente, e o que fazem as opções earliest e latest de auto.offset.reset.
    5. Por que a ordem de implantação entre publicador e assinante importa.
    
    Depois me apresente cinco situações, uma por vez, e pergunte se a assinatura deve ser durável ou efêmera: invalidar um cache local, baixar o estoque, enviar e-mail de confirmação, atualizar uma tela aberta no navegador e registrar a venda para análise. Corrija cada resposta.
  • O evento como contrato

    Quero entender por que o formato de um evento vira um contrato entre serviços e como evoluí-lo sem quebrar ninguém.
    
    Pergunte em qual linguagem eu escrevo os assinantes.
    
    Explique:
    1. Por que o publicador pode quebrar assinantes que ele nem conhece.
    2. Quais mudanças são compatíveis e quais não são: acrescentar, remover, renomear, mudar o tipo e mudar o significado de um campo.
    3. O princípio do leitor tolerante e como a biblioteca de JSON da minha linguagem trata campos desconhecidos por padrão.
    4. A diferença entre um evento de notificação e um evento com o estado, e como cada um afeta o acoplamento.
    5. Como publicar uma versão nova de um evento ao lado da antiga.
    
    Depois me mostre quatro mudanças num evento "pedido.criado" e pergunte, para cada uma, se ela quebra os assinantes. Corrija as minhas respostas.