DevGuideTK

Publicação e assinatura

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

As decisões de publicação e assinatura são quatro: qual intermediário usar, o que vai dentro do evento, como evoluir o formato sem quebrar os assinantes e quando publicar um evento em vez de chamar o outro serviço.

Os intermediários

Assinatura durávelReler o passadoFiltro no intermediário
Redis Pub/SubNãoNãoPor padrão de canal (PSUBSCRIBE)
Redis StreamsSim, com grupos de consumidoresSim, dentro do limite do streamNão
RabbitMQSim, uma fila por assinaturaNão (salvo com streams do RabbitMQ)Sim, por chave de roteamento ou cabeçalhos
KafkaSim, um grupo por assinaturaSim, pelo tempo de retençãoNão
SNS + SQSSim, uma fila SQS por assinaturaNãoSim, política de filtro
Google Pub/SubSimSó com retenção no tópico e seekSim, por atributos
Azure Service BusSimNãoSim, filtros SQL e de correlação

Redis Pub/Sub

Entrega imediata a quem está conectado e nada mais. Serve para avisos que perdem o valor em segundos: notificar as instâncias de um servidor WebSocket de que há uma mensagem nova para um usuário, ou mandar todas as instâncias invalidarem um cache local. Não serve para nada que precise acontecer de fato.

Redis Streams

Um log dentro do Redis, com grupos de consumidores e confirmação por mensagem. É uma opção leve para quem já usa Redis e tem volume modesto. O limite é a memória: o stream precisa ser aparado (MAXLEN), e o que foi aparado não volta.

RabbitMQ

O publicador envia para uma troca (exchange), e cada assinante liga a sua própria fila a ela. O tipo da troca define quem recebe:

Tipo de trocaQuem recebe
fanoutTodas as filas ligadas, sem olhar a chave
topicAs filas cujo padrão casa com a chave de roteamento (pedido.*, pedido.#)
directAs filas ligadas com a chave exata
headersAs filas cujos cabeçalhos casam

A troca topic é a mais usada para eventos de negócio, porque permite que cada assinante escolha os tipos de evento que quer.

Kafka

O tópico é um log particionado, e cada grupo de consumidores lê todas as partições, dividindo-as entre as instâncias do grupo. Um grupo novo pode ler o histórico desde o início, o que resolve a carga inicial de um serviço novo e permite reprocessar depois de corrigir um defeito. O custo é a operação e a falta de filtros: cada assinante lê tudo do tópico.

O paralelismo de um grupo é limitado pelo número de partições: num tópico com seis partições, a sétima instância do grupo fica parada.

Serviços gerenciados

Na AWS, o padrão é SNS na frente de filas SQS: o tópico SNS distribui, e cada serviço tem a sua fila SQS, que guarda as mensagens, divide o trabalho entre as instâncias e tem fila de mensagens mortas. Publicar direto do SNS para uma função ou um endereço HTTP também é possível, mas perde o amortecimento da fila.

O Google Pub/Sub e as assinaturas do Azure Service Bus já são duráveis e divisíveis entre instâncias, sem uma fila à parte.

Como escolher

SituaçãoEscolha
Avisos efêmeros entre instâncias do mesmo serviçoRedis Pub/Sub
Poucos assinantes, volume modesto, já usa RedisRedis Streams
Eventos de negócio com filtros por tipo, sem precisar do históricoRabbitMQ com troca topic
Muitos assinantes, volume alto, serviços novos que precisam do históricoKafka
Está na AWS e não quer operar nadaSNS + SQS
Está no Google Cloud ou no AzurePub/Sub ou Service Bus

O que vai dentro do evento

Há dois estilos, e a escolha muda o acoplamento.

Evento de notificaçãoEvento com o estado
ConteúdoO fato e os identificadores: { "pedidoId": 981 }O fato e os dados relevantes: itens, cliente, total
O assinante que precisa de detalhesConsulta o publicadorJá tem tudo na mensagem
AcoplamentoEm tempo de execução: o publicador precisa estar no arNo formato: mais campos, mais contrato
Dados desatualizadosNão, a consulta traz o estado atualSim, a mensagem mostra o estado no momento do evento
Carga no publicadorUma consulta por assinante, por eventoNenhuma

Na prática, o evento com o estado (event-carried state transfer) é o mais comum, porque mantém os assinantes funcionando quando o publicador está fora do ar. Inclua o que a maioria dos assinantes precisa, não tudo o que existe na tabela.

O envelope

Todo evento, qualquer que seja o conteúdo, precisa de alguns campos fixos:

CampoPara quê
idIdentificador único, para os assinantes descartarem reentregas
tipopedido.criado, para rotear e para o assinante saber como ler
versaoA versão do formato do campo dados
ocorridoEmQuando o fato aconteceu, não quando a mensagem foi publicada
origemO serviço que publicou
dadosO conteúdo do evento

O padrão CloudEvents, da CNCF, define esse envelope de forma padronizada (id, source, type, time, data) e tem bibliotecas para as linguagens principais. Vale adotar quando eventos atravessam equipes ou empresas.

Evoluir sem quebrar

PráticaComo
Só acrescentar camposCampos novos são opcionais; nada é removido nem renomeado
Leitor toleranteO assinante ignora campos desconhecidos e lê só o que usa
Versão no eventoUm campo versao; numa mudança incompatível, um tipo novo publicado ao lado do antigo
Período de convivênciaO publicador publica as duas versões até o último assinante migrar
Registro de esquemasUm schema registry (Confluent, Apicurio, AWS Glue) recusa a publicação de um formato incompatível

O registro de esquemas é comum com Kafka, usando Avro, Protobuf ou JSON Schema. Ele transforma a compatibilidade numa verificação automática, em vez de uma promessa.

Evento, comando ou chamada direta

SituaçãoUse
Um fato que vários serviços podem querer saberEvento publicado num tópico
Um trabalho com um único executor, que pode esperarComando numa fila
O chamador precisa da resposta para continuarChamada direta, síncrona
A operação precisa ser tudo ou nada entre serviçosUma saga, com eventos ou comandos (tópico próprio na trilha)

Um sinal de que um evento virou comando disfarçado: o nome está no imperativo (enviar-email), só um assinante faz sentido, e o publicador espera que algo específico aconteça. Nesse caso, uma fila deixa a intenção explícita.

Publicar sem perder

Publicação e assinatura herda o problema descrito no tópico de filas: gravar no banco e depois publicar pode deixar um pedido que nenhum assinante conhece. A solução é a mesma, o padrão outbox. Com vários assinantes, o problema pesa mais, porque um evento perdido deixa vários serviços inconsistentes ao mesmo tempo.

Resumo da decisão

  1. Intermediário: Redis Pub/Sub só para avisos efêmeros; RabbitMQ topic, SNS + SQS ou Pub/Sub para eventos de negócio; Kafka quando há muitos assinantes ou é preciso reler o histórico.
  2. Assinatura: uma por serviço, durável, com nome fixo e criada antes de o publicador começar.
  3. Instâncias: as réplicas de um serviço dividem a mesma assinatura.
  4. Conteúdo: evento com o estado necessário, num envelope com id, tipo, versao e ocorridoEm.
  5. Evolução: só acrescentar campos, assinantes tolerantes e versão nova ao lado da antiga quando a mudança for incompatível.
  6. Confiabilidade: outbox no publicador e idempotência em cada assinante.

O próximo nível mostra um publicador e assinantes com a troca topic do RabbitMQ nas cinco linguagens.

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.

  • Escolher o intermediário para eventos

    Preciso escolher como distribuir eventos entre os serviços do meu sistema.
    
    Faça estas perguntas, uma por vez, e espere as respostas:
    - Quantos serviços vão publicar e quantos vão assinar eventos?
    - Algum evento pode ser perdido sem prejuízo, ou todos precisam ser processados?
    - Um serviço novo vai precisar conhecer os eventos antigos?
    - Os assinantes precisam filtrar os eventos que recebem?
    - Qual é o volume esperado? O sistema está numa nuvem? A equipe já opera algum intermediário?
    
    Com as respostas, compare para o meu caso o Redis Pub/Sub, o Redis Streams, o RabbitMQ com troca topic, o Kafka e o serviço gerenciado da minha nuvem. Seja concreto sobre o que cada opção garante e o que custa operar.
    
    Termine com a recomendação, os riscos dela e os sinais que indicariam a necessidade de mudar no futuro.
  • Desenhar o formato dos eventos

    Quero desenhar o formato dos eventos do meu sistema.
    
    Pergunte qual é o domínio do sistema e peça três exemplos de fatos que outros serviços precisam saber.
    
    Para esses exemplos, conduza por etapas:
    1. Dê nomes aos eventos, no passado, e diga se algum deles é na verdade um comando.
    2. Monte o envelope com id, tipo, versão, data do fato e origem, e explique cada campo. Compare com o padrão CloudEvents.
    3. Decida, com a minha participação, entre evento de notificação e evento com o estado, e quais campos entram em dados.
    4. Defina as regras de evolução: o que é permitido mudar e como publicar uma versão incompatível.
    5. Mostre como documentar os eventos e os assinantes, com AsyncAPI ou um registro de esquemas.
    
    Em cada etapa, pergunte o que eu faria antes de mostrar a sua sugestão.
  • Evento, comando ou chamada direta

    Quero aprender a decidir quando um serviço deve publicar um evento, enviar um comando para uma fila ou chamar outro serviço diretamente.
    
    Explique os critérios: quantos interessados existem, se o chamador precisa da resposta, quem decide o que acontece e o que acontece se o outro lado estiver fora do ar.
    
    Depois me apresente seis situações, uma por vez, e pergunte o que eu usaria:
    1. Avisar estoque, e-mail e análise de que um pedido foi criado.
    2. Gerar o PDF de um relatório pedido pelo usuário.
    3. Consultar o preço atual de um produto para montar o carrinho.
    4. Avisar que o endereço de um cliente mudou.
    5. Cobrar o cartão durante a finalização da compra.
    6. Mandar enviar o e-mail de recuperação de senha.
    
    Corrija cada resposta explicando o motivo e aponte os eventos que seriam comandos disfarçados.