Pub/sub é um padrão de mensageria em que publicadores enviam mensagens a tópicos em um broker, e cada assinante do tópico recebe uma cópia. Todo o seu valor está no que as partes não sabem: publicadores nunca descobrem quem está ouvindo, assinantes nunca descobrem quem enviou — a indireção do broker os desacopla no espaço (sistemas diferentes), no tempo (assinantes podem estar offline) e na sincronização (o publish retorna imediatamente). Adicione assinantes sem tocar nos publicadores; esse é o superpoder discreto do padrão.
Principais pontos
| Pergunta | Resposta |
|---|---|
| O elenco | Publicador → mensagem → tópico → broker → assinantes (cada um recebe uma cópia) |
| vs. uma fila | Uma fila distribui o trabalho a um consumidor; pub/sub o duplica para todos |
| vs. observer | Observer é in-process e síncrono; pub/sub tem broker e é assíncrono |
| A postura padrão | Entrega at-least-once + handlers idempotentes |
| As letras miúdas | Sem respostas, sem ordem por padrão, sem transações atômicas através do broker |
O padrão em código
Toda API de pub/sub se reduz a dois verbos, seja qual for o broker:
// Assinantes — registram interesse em um tópico
broker.subscribe('orders.paid', (msg) => fulfill(msg)); // serviço A
broker.subscribe('orders.paid', (msg) => notifyUser(msg)); // serviço B — sua própria cópia
// Publicador — dispara e esquece
broker.publish('orders.paid', { orderId: 'o-1187' });
// O publicador nunca descobre quem recebeu — nem se alguém recebeu.
A mesma forma na borda do cliente, onde uma assinatura de live query é o tópico e uma escrita no banco de dados é o publish:
// JavaScript / Node.js — Back4app JS SDK
// Pub/sub at the edge: live queries subscribe, saves publish
// Subscriber — register interest in a topic
const topic = new Parse.Query('Event');
topic.equalTo('topic', 'orders');
const sub = await topic.subscribe();
sub.on('create', (event) => handle(event.get('payload')));
// Publisher — fire and forget; no knowledge of subscribers
const event = new Parse.Object('Event');
event.set('topic', 'orders');
event.set('payload', { orderId: 'o-1187', status: 'paid' });
await event.save(); // Flutter / Dart — Back4app Flutter SDK
// Pub/sub at the edge: live queries subscribe, saves publish
// Subscriber — register interest in a topic
final liveQuery = LiveQuery();
final topic = QueryBuilder<ParseObject>(ParseObject('Event'))
..whereEqualTo('topic', 'orders');
final sub = await liveQuery.client.subscribe(topic);
sub.on(LiveQueryEvent.create, (event) => handle(event.get('payload')));
// Publisher — fire and forget; no knowledge of subscribers
final event = ParseObject('Event')
..set('topic', 'orders')
..set('payload', {'orderId': 'o-1187', 'status': 'paid'});
await event.save(); // iOS / Swift — Back4app Swift SDK
// Pub/sub at the edge: live queries subscribe, saves publish
// Subscriber — register interest in a topic
let topic = Event.query("topic" == "orders")
let sub = try await topic.subscribe()
sub.handleEvent { _, e in
if case .created(let event) = e { handle(event.payload) }
}
// Publisher — fire and forget; no knowledge of subscribers
var event = Event()
event.topic = "orders"
event.payload = ["orderId": "o-1187", "status": "paid"]
try await event.save() // Android / Kotlin — Back4app Android SDK
// Pub/sub at the edge: live queries subscribe, saves publish
// Subscriber — register interest in a topic
val client = ParseLiveQueryClient.Factory.getClient()
val topic = ParseQuery.getQuery<ParseObject>("Event")
topic.whereEqualTo("topic", "orders")
val sub = client.subscribe(topic)
sub.handleEvent(SubscriptionHandling.Event.CREATE) { _, event ->
handle(event.getJSONObject("payload"))
}
// Publisher — fire and forget; no knowledge of subscribers
val event = ParseObject("Event")
event.put("topic", "orders")
event.put("payload", JSONObject(mapOf("orderId" to "o-1187", "status" to "paid")))
event.save() Como o pub/sub funciona
O broker faz quatro trabalhos: aceitar publishes e retornar imediatamente; casar cada mensagem com as assinaturas (por nome de tópico, hierarquicamente — orders.* — ou por atributos de conteúdo); entregar uma cópia independente por assinante, com retry conforme o nível de garantia; e reter mensagens para assinantes offline onde a durabilidade estiver configurada — o desacoplamento no tempo que deixa um serviço fazer deploy ao meio-dia e recuperar o que perdeu. Uma nota de vocabulário que vale a frase: uma mensagem é o envelope; um evento — “isto aconteceu, no passado” — é o que o pub/sub normalmente carrega, e se ele carrega só o fato ou o estado completo é a distinção de Fowler entre event notification e event-carried state transfer.
Pub/sub vs. filas de mensagens vs. padrão observer
As duas confusões, resolvidas em uma tabela:
| Pub/sub | Fila de mensagens | Observer | |
|---|---|---|---|
| Entrega | Cada assinante recebe uma cópia | Exatamente um worker consome cada mensagem | O sujeito chama cada observador |
| Propósito | Transmitir eventos | Distribuir trabalho | Reagir a estado in-process |
| Acoplamento | Nenhum — mediado pelo broker | Nenhum — mediado pela fila | Referências diretas a objetos |
| Timing | Assíncrono | Assíncrono | Geralmente síncrono |
| Fronteira | Entre processos/sistemas | Entre workers | Dentro de um processo |
| Forma canônica | 1 evento → N reações | N jobs → M workers | 1 objeto → seus listeners |
A frase que sobrevive à reunião: uma fila distribui o trabalho; pub/sub o duplica. E observer não é um “pub/sub pequeno” — a indireção do broker é exatamente o que falta ao observer, e por isso refatorar um observer para pub/sub é uma mudança arquitetural, não uma renomeação. Na prática os brokers oferecem as duas formas: consumer groups transformam um tópico em fila por grupo (a jogada de assinatura do Kafka), exchanges de fanout transformam um sistema de filas em pub/sub.
Garantias de entrega: at-most-once vs. at-least-once vs. exactly-once
| Garantia | Modo de falha | Custo | Seu handler deve | Escolha quando |
|---|---|---|---|---|
| At-most-once | Mensagens podem sumir | O mais barato e rápido | Tolerar lacunas | Dados efêmeros: presença, cotações, métricas |
| At-least-once | Duplicatas chegam | Acks + retries | Ser idempotente | O padrão para eventos de negócio |
| Exactly-once | Restrito, complexo | Dedup + coordenação | Ainda ser idempotente | Caminhos estreitos onde o broker suporta |
Os níveis de QoS 0/1/2 do MQTT mapeiam exatamente para esses três — uma confirmação elegante de que a tricotomia é fundamental, não marketing de fornecedor. A verdade de engenharia: exactly-once de ponta a ponta é inatingível no caso geral (o problema dos Dois Generais usando crachá de broker); sistemas que o anunciam combinam entrega at-least-once com deduplicação. Por isso o contrato real do padrão está escrito no seu handler: at-least-once + idempotência — deduplique por ID de mensagem, use upserts com chave na entidade ou cheque a versão antes de aplicar — transforma duplicatas de bugs em no-ops.
Ordenação, dead letters e design de tópicos
Três realidades operacionais que os diagramas introdutórios pulam. Ordenação: fan-out para consumidores paralelos não tem ordem global; brokers a restauram por partição ou chave de ordenação ao preço do paralelismo — desenhe handlers que apliquem eventos por ID e versão da entidade, não pela sequência de chegada, e o problema quase se dissolve. Mensagens venenosas: uma mensagem cujo processamento sempre lança erro vai tentar de novo para sempre sob at-least-once; limite as tentativas de entrega e roteie as falhas para uma fila de dead-letter para triagem, ou um evento ruim trava o tópico. Design de tópicos: nomeie hierarquicamente (orders.paid, orders.refunded, curinga orders.*), mantenha a granularidade no nível em que os assinantes de fato diferem e evolua os schemas de mensagem aditivamente — assinantes ignoram campos desconhecidos; quebras ganham um novo tópico versionado (orders.v2) com janela de migração, exatamente como uma versão de API.
Casos de uso comuns
- Integração de microsserviços — serviços de times diferentes reagindo aos eventos uns dos outros sem chamadas diretas nem agendas de deploy compartilhadas.
- Recursos de tempo real no cliente — fan-out de chat, dashboards ao vivo, presença: pub/sub sobre WebSockets na borda.
- Telemetria de IoT — milhares de dispositivos publicando via MQTT; consumidores de alertas a analytics assinando de forma independente.
- Invalidação de cache e replicação — uma escrita publicada, cada cache e réplica notificados.
- Pipelines de processamento paralelo — um evento disparando fulfillment, notificação e analytics ao mesmo tempo, cada um no próprio ritmo.
Você deveria usar pub/sub? Matriz de decisão
| Pub/sub encaixa quando… | Prefira outra coisa quando… |
|---|---|
| Um evento interessa a vários consumidores independentes | Um único consumidor: uma fila, ou só uma chamada |
| Produtores e consumidores fazem deploy separadamente | Quem chama precisa da resposta agora → request-response |
| Consistência eventual é aceitável | Atomicidade entre as partes é exigida → transações/Saga |
| Assinantes entram e saem ao longo do tempo | Ordem global estrita é inegociável |
| Carga com picos precisa de buffer entre sistemas | O sistema inteiro cabe em um processo → observer |
Limitações e trade-offs
- O debug atravessa um broker. “Quem consumiu isto e por que falhou?” exige IDs de correlação e tracing; o desacoplamento que liberta os times também esconde a causalidade.
- Sem respostas, por design. Fluxos que precisam de resposta têm de modelá-la como novas mensagens — latência extra e máquinas de estado onde antes bastava uma chamada de função.
- O broker é infraestrutura. Disponibilidade, capacidade, segurança e políticas de retenção da camada de mensageria viram trabalho de plataforma; um broker fora do ar é um multiplicador de outages.
- A cegueira do publicador corta dos dois lados. Publicadores não observam a saúde dos assinantes; um consumidor falhando em silêncio perde dados, a menos que exista monitoramento de dead-letter.
- Garantias custam throughput. Chaves de ordenação serializam, exactly-once coordena, durabilidade persiste — cada reforço de semântica gasta performance; compre só o que os dados precisam.
Pub/Sub no Back4app
O Back4app é uma plataforma open-source de Backend as a Service (BaaS) que combina banco de dados gerenciado, APIs REST e GraphQL geradas automaticamente, autenticação, armazenamento de arquivos e funções serverless com Cloud Code. O padrão aparece aqui na borda do cliente: as Live Queries são pub/sub em que a query é o tópico — assinantes registram um predicado, qualquer escrita que casar é publicada para cada assinante por uma frota gerenciada de WebSockets, com permissões checadas por assinante e fan-out por conta da plataforma, como nas abas de código acima. Reações server-side se compõem do mesmo jeito: triggers de Cloud Code assinam mudanças de dados (afterSave como assinatura de tópico em espírito), e funções agendadas drenam trabalho publicado como linhas. Para streaming pesado entre serviços você ainda buscaria um broker dedicado como Kafka ou RabbitMQ — mas, para os casos comuns de produto, o transmitir-ao-mudar já vem com o backend.
Perguntas frequentes
O que é pub/sub em termos simples?
Um padrão de mensageria em que remetentes (publicadores) postam mensagens em tópicos nomeados em um broker, e cada componente que assinou aquele tópico recebe a própria cópia — publicadores e assinantes nunca sabem uns dos outros. Pense em transmissão de rádio: a estação transmite numa frequência; quem sintonizar, ouve.
Qual a diferença entre pub/sub e fila de mensagens?
Uma fila distribui trabalho; pub/sub o duplica. Em uma fila, cada mensagem é consumida por exatamente um worker — a ferramenta para espalhar jobs por um pool. No pub/sub, cada assinante do tópico recebe uma cópia — a ferramenta para transmitir eventos a interessados independentes.
Qual a diferença entre pub/sub e o padrão observer?
Observer é in-process: o sujeito guarda referências diretas aos observadores e normalmente os chama de forma síncrona. Pub/sub insere um broker entre as partes, deixando-as totalmente desacopladas, tipicamente assíncronas e capazes de viver em processos, linguagens e deploys diferentes. Um é um padrão de projeto dentro de um app; o outro é uma arquitetura entre sistemas.
O que é um tópico no pub/sub?
Um canal lógico nomeado que categoriza mensagens: publicadores endereçam o tópico, e o broker entrega cada mensagem a todos os assinantes daquele tópico. A filtragem pode ser por tópico (assinar pelo nome, muitas vezes hierarquicamente — orders.*) ou por conteúdo, em que o broker compara atributos da mensagem.
Quais garantias de entrega o pub/sub oferece?
Três níveis. At-most-once: dispara e esquece — rápido, pode perder mensagens. At-least-once: tenta de novo até confirmar — o padrão comum, gera duplicatas ocasionais. Exactly-once: deduplicação mais coordenação — caro, restrito e impossível de garantir de ponta a ponta no caso geral. A postura prática: entrega at-least-once com handlers idempotentes.
O pub/sub garante a ordem das mensagens?
Não por padrão — as mensagens se espalham para assinantes paralelos, e ordem global briga com paralelismo. Brokers conseguem ordenar dentro de uma partição ou chave de ordenação, ao custo de throughput. O design robusto assume ordem apenas onde ela foi explicitamente configurada e escreve handlers que toleram chegada fora de ordem.
Kafka é pub/sub?
Sim, com um detalhe: o Apache Kafka implementa a semântica de publicação-assinatura sobre um commit log particionado, durável e com replay. Dentro de um consumer group ele se comporta como fila (cada mensagem vai a um membro); entre groups, como pub/sub (cada group recebe uma cópia) — por isso costuma ser chamado de event streaming.
Quando não usar pub/sub?
Quando quem chama precisa de uma resposta imediata (request-response), quando operações precisam ser atômicas entre produtor e consumidores (um broker não entra em uma transação — veja o padrão Saga), quando ordem global estrita é obrigatória, ou quando o sistema é pequeno o bastante para um broker adicionar mais superfície operacional do que remove.