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ável | Reler o passado | Filtro no intermediário | |
|---|---|---|---|
| Redis Pub/Sub | Não | Não | Por padrão de canal (PSUBSCRIBE) |
| Redis Streams | Sim, com grupos de consumidores | Sim, dentro do limite do stream | Não |
| RabbitMQ | Sim, uma fila por assinatura | Não (salvo com streams do RabbitMQ) | Sim, por chave de roteamento ou cabeçalhos |
| Kafka | Sim, um grupo por assinatura | Sim, pelo tempo de retenção | Não |
| SNS + SQS | Sim, uma fila SQS por assinatura | Não | Sim, política de filtro |
| Google Pub/Sub | Sim | Só com retenção no tópico e seek | Sim, por atributos |
| Azure Service Bus | Sim | Não | Sim, 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 troca | Quem recebe |
|---|---|
fanout | Todas as filas ligadas, sem olhar a chave |
topic | As filas cujo padrão casa com a chave de roteamento (pedido.*, pedido.#) |
direct | As filas ligadas com a chave exata |
headers | As 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ção | Escolha |
|---|---|
| Avisos efêmeros entre instâncias do mesmo serviço | Redis Pub/Sub |
| Poucos assinantes, volume modesto, já usa Redis | Redis Streams |
| Eventos de negócio com filtros por tipo, sem precisar do histórico | RabbitMQ com troca topic |
| Muitos assinantes, volume alto, serviços novos que precisam do histórico | Kafka |
| Está na AWS e não quer operar nada | SNS + SQS |
| Está no Google Cloud ou no Azure | Pub/Sub ou Service Bus |
O que vai dentro do evento
Há dois estilos, e a escolha muda o acoplamento.
| Evento de notificação | Evento com o estado | |
|---|---|---|
| Conteúdo | O fato e os identificadores: { "pedidoId": 981 } | O fato e os dados relevantes: itens, cliente, total |
| O assinante que precisa de detalhes | Consulta o publicador | Já tem tudo na mensagem |
| Acoplamento | Em tempo de execução: o publicador precisa estar no ar | No formato: mais campos, mais contrato |
| Dados desatualizados | Não, a consulta traz o estado atual | Sim, a mensagem mostra o estado no momento do evento |
| Carga no publicador | Uma consulta por assinante, por evento | Nenhuma |
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:
| Campo | Para quê |
|---|---|
id | Identificador único, para os assinantes descartarem reentregas |
tipo | pedido.criado, para rotear e para o assinante saber como ler |
versao | A versão do formato do campo dados |
ocorridoEm | Quando o fato aconteceu, não quando a mensagem foi publicada |
origem | O serviço que publicou |
dados | O 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ática | Como |
|---|---|
| Só acrescentar campos | Campos novos são opcionais; nada é removido nem renomeado |
| Leitor tolerante | O assinante ignora campos desconhecidos e lê só o que usa |
| Versão no evento | Um campo versao; numa mudança incompatível, um tipo novo publicado ao lado do antigo |
| Período de convivência | O publicador publica as duas versões até o último assinante migrar |
| Registro de esquemas | Um 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ção | Use |
|---|---|
| Um fato que vários serviços podem querer saber | Evento publicado num tópico |
| Um trabalho com um único executor, que pode esperar | Comando numa fila |
| O chamador precisa da resposta para continuar | Chamada direta, síncrona |
| A operação precisa ser tudo ou nada entre serviços | Uma 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
- 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. - Assinatura: uma por serviço, durável, com nome fixo e criada antes de o publicador começar.
- Instâncias: as réplicas de um serviço dividem a mesma assinatura.
- Conteúdo: evento com o estado necessário, num envelope com
id,tipo,versaoeocorridoEm. - Evolução: só acrescentar campos, assinantes tolerantes e versão nova ao lado da antiga quando a mudança for incompatível.
- 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.