Il pub/sub è un pattern di messaggistica in cui i publisher inviano messaggi a dei topic su un broker e ogni subscriber di un topic ne riceve una copia. Tutto il suo valore sta in ciò che le parti non sanno: i publisher non scoprono mai chi ascolta, i subscriber non scoprono mai chi ha inviato – l’indirezione del broker li disaccoppia nello spazio (sistemi diversi), nel tempo (i subscriber possono essere offline) e nella sincronizzazione (la pubblicazione restituisce subito il controllo). Si aggiungono subscriber senza toccare i publisher: è il superpotere discreto del pattern.
Punti chiave
| Domanda | Risposta |
|---|---|
| Gli attori | Publisher → messaggio → topic → broker → subscriber (ognuno riceve una copia) |
| vs. una coda | Una coda ripartisce il lavoro verso un solo consumatore; il pub/sub lo duplica verso tutti |
| vs. observer | L’observer è interno al processo e sincrono; il pub/sub passa da un broker ed è asincrono |
| La posizione predefinita | Consegna at-least-once + handler idempotenti |
| Le clausole in piccolo | Nessuna risposta, nessun ordine predefinito, nessuna transazione atomica attraverso il broker |
Il pattern in codice
Qualunque API pub/sub si riduce a due verbi, indipendentemente dal broker:
// Subscriber – dichiarano interesse per un topic
broker.subscribe('orders.paid', (msg) => fulfill(msg)); // servizio A
broker.subscribe('orders.paid', (msg) => notifyUser(msg)); // servizio B – copia propria
// Publisher – fire and forget
broker.publish('orders.paid', { orderId: 'o-1187' });
// Il publisher non scopre mai chi l'ha ricevuto – né se qualcuno l'ha ricevuto.
La stessa forma sul lato client, dove una sottoscrizione live query fa da topic e una scrittura sul database fa da pubblicazione:
// 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() Come funziona il pub/sub
Il broker svolge quattro compiti: accettare le pubblicazioni e restituire subito il controllo; far corrispondere ogni messaggio alle sottoscrizioni (per nome del topic, in modo gerarchico – orders.* – oppure per attributi di contenuto); consegnare una copia indipendente a ogni subscriber, con retry secondo il suo livello di garanzia; e conservare i messaggi per i subscriber offline dove la durabilità è configurata – il disaccoppiamento temporale che permette a un servizio rilasciato a mezzogiorno di recuperare ciò che ha perso. Una nota di vocabolario che merita una frase: il messaggio è la busta; l’evento – “questo è accaduto”, al passato – è ciò che il pub/sub trasporta di solito, e il fatto che porti il solo accaduto oppure lo stato completo è la distinzione di Fowler fra event notification ed event-carried state transfer.
Pub/sub vs. code di messaggi vs. pattern observer
Le due confusioni, risolte in una tabella:
| Pub/sub | Coda di messaggi | Observer | |
|---|---|---|---|
| Consegna | Ogni subscriber riceve una copia | Un solo worker consuma ciascun messaggio | Il soggetto chiama ogni observer |
| Ruolo | Diffondere eventi | Ripartire il lavoro | Reagire a uno stato dentro il processo |
| Accoppiamento | Nessuno – mediato dal broker | Nessuno – mediato dalla coda | Riferimenti diretti fra oggetti |
| Tempi | Asincrono | Asincrono | Di solito sincrono |
| Confine | Fra processi e sistemi | Fra worker | Dentro un solo processo |
| Forma canonica | 1 evento → N reazioni | N job → M worker | 1 oggetto → i suoi listener |
La formula che sopravvive alla riunione: una coda ripartisce il lavoro; il pub/sub lo duplica. E l’observer non è un “pub/sub in piccolo” – l’indirezione del broker è esattamente ciò che all’observer manca, ed è per questo che trasformare un observer in pub/sub è un cambio di architettura, non una rinominazione. In pratica i broker offrono entrambe le forme: i consumer group trasformano un topic in una coda per gruppo (la mossa distintiva di Kafka), gli exchange fanout trasformano un sistema di code in pub/sub.
Garanzie di consegna: at-most-once vs. at-least-once vs. exactly-once
| Garanzia | Modalità di guasto | Costo | L’handler deve | Sceglierla quando |
|---|---|---|---|---|
| At-most-once | I messaggi possono svanire | Il più economico, il più rapido | Tollerare i buchi | Dati effimeri: presenza, ticker, metriche |
| At-least-once | Arrivano duplicati | Ack + retry | Essere idempotente | È la scelta predefinita per gli eventi di business |
| Exactly-once | Vincolato, complesso | Deduplicazione + coordinamento | Restare idempotente | Percorsi ristretti in cui il broker lo consente |
I livelli QoS 0/1/2 di MQTT corrispondono esattamente a questi tre – una conferma netta che la tricotomia è fondamentale e non un argomento di marketing. La verità di chi progetta: l’exactly-once da un capo all’altro è inarrivabile nel caso generale (il problema dei due generali con il distintivo del broker); i sistemi che lo annunciano combinano consegna at-least-once e deduplicazione. Per questo il vero contratto del pattern si scrive nel proprio handler: at-least-once + idempotenza – deduplicare su un ID del messaggio, usare upsert con chiave sull’entità oppure verificare la versione prima di applicare – trasforma i duplicati da bug in no-op.
Ordine, dead letter e progettazione dei topic
Tre realtà operative che i diagrammi introduttivi saltano. L’ordine: il fan-out verso consumatori paralleli non ha ordine globale; i broker lo ripristinano per partizione o per chiave di ordinamento, al prezzo del parallelismo – conviene progettare gli handler perché applichino gli eventi per ID dell’entità e per versione, non per ordine di arrivo, e il problema si dissolve in buona parte. I messaggi avvelenati: un messaggio la cui elaborazione solleva sempre un’eccezione viene ritentato all’infinito in at-least-once; conviene limitare i tentativi di consegna e instradare i fallimenti verso una dead letter queue per il triage, altrimenti un solo evento difettoso blocca il topic. La progettazione dei topic: nomi gerarchici (orders.paid, orders.refunded, jolly orders.*), granularità al livello su cui i subscriber differiscono davvero, ed evoluzione additiva degli schemi dei messaggi – i subscriber ignorano i campi sconosciuti; le modifiche incompatibili ricevono un nuovo topic versionato (orders.v2) con una finestra di migrazione, esattamente come una versione di API.
Casi d’uso tipici
- Integrazione di microservizi – servizi di team diversi che reagiscono agli eventi degli altri senza chiamate dirette né calendari di deployment condivisi.
- Funzionalità in tempo reale lato client – fan-out di chat, dashboard in diretta, presenza: pub/sub su WebSocket lato client.
- Telemetria IoT – migliaia di dispositivi che pubblicano via MQTT; consumatori che vanno dagli avvisi all’analytics e si sottoscrivono in modo indipendente.
- Invalidazione della cache e replica – una scrittura pubblicata, e ogni cache e ogni replica avvisate.
- Pipeline di elaborazione in parallelo – un evento che avvia insieme evasione, notifica e analytics, ognuna con i propri tempi.
Conviene usare il pub/sub? Matrice decisionale
| Il pub/sub va bene quando… | Meglio qualcos’altro quando… |
|---|---|
| Un evento interessa più consumatori indipendenti | C’è un solo consumatore: una coda, o una semplice chiamata |
| Produttori e consumatori rilasciano in modo indipendente | Chi chiama ha bisogno della risposta subito → richiesta-risposta |
| L’eventual consistency (coerenza finale) è accettabile | Serve atomicità fra le parti → transazioni o saga |
| I subscriber vanno e vengono col tempo | Un ordine globale stretto non è negoziabile |
| Un carico a picchi va ammortizzato fra sistemi | Tutto il sistema sta in un solo processo → observer |
Limiti e trade-off
- Il debug attraversa un broker. “Chi ha consumato questo, e perché è fallito?” esige ID di correlazione e tracing; il disaccoppiamento che libera i team nasconde anche la causalità.
- Nessuna risposta, per progetto. I workflow che hanno bisogno di risposte devono modellarle come messaggi ulteriori – latenza in più e macchine a stati dove bastava una chiamata di funzione.
- Il broker è infrastruttura. Disponibilità, capacità, sicurezza e politiche di conservazione del livello di messaggistica diventano lavoro di piattaforma; un broker fuori servizio moltiplica gli incidenti.
- La cecità del publisher è un’arma a doppio taglio. I publisher non vedono la salute dei subscriber; un consumatore che fallisce in silenzio perde dati se non esiste un monitoring delle dead letter.
- Le garanzie costano throughput. Le chiavi di ordinamento serializzano, l’exactly-once coordina, la durabilità persiste – ogni rafforzamento della semantica si paga in prestazioni; conviene comprare solo ciò di cui i dati hanno davvero bisogno.
Il pub/sub su Back4app
Back4app è una piattaforma open-source di Backend as a Service (BaaS) che combina database gestito, API REST e GraphQL generate automaticamente, autenticazione, archiviazione di file e funzioni serverless con Cloud Code. Il pattern compare qui sul lato client: le Live Queries sono pub/sub in cui la query è il topic – i subscriber registrano un predicato, e qualsiasi scrittura che corrisponda a quel predicato viene pubblicata verso ogni subscriber su una flotta gestita di WebSocket, con i permessi verificati per singolo subscriber e il fan-out gestito dalla piattaforma, come nelle schede di codice qui sopra. Le reazioni lato server si compongono allo stesso modo: i trigger Cloud Code si sottoscrivono alle modifiche dei dati (afterSave come sottoscrizione a un topic, nello spirito) e le funzioni pianificate smaltiscono il lavoro pubblicato sotto forma di righe. Per lo streaming intenso fra servizi la scelta resta un broker dedicato come Kafka o RabbitMQ – ma per i casi di prodotto più comuni la diffusione a ogni cambiamento arriva insieme al backend.
Domande frequenti
Che cos'è il pub/sub in parole semplici?
Un pattern di messaggistica in cui i mittenti (i publisher) pubblicano messaggi su topic con un nome ospitati da un broker, e ogni componente che si è sottoscritto a quel topic riceve la propria copia – publisher e subscriber non si conoscono mai. Vale l'immagine della radio: la stazione trasmette su una frequenza; chi si sintonizza, ascolta.
Qual è la differenza tra il pub/sub e una coda di messaggi?
Una coda ripartisce il lavoro; il pub/sub lo duplica. In una coda ogni messaggio viene consumato da un solo worker – lo strumento per distribuire job su un pool. Nel pub/sub ogni subscriber del topic riceve una copia – lo strumento per diffondere eventi verso parti interessate in modo indipendente l'una dall'altra.
Qual è la differenza tra il pub/sub e il pattern observer?
L'observer vive dentro un solo processo: il soggetto conserva riferimenti diretti ai propri observer e di solito li chiama in modo sincrono. Il pub/sub intercala un broker fra le parti, le disaccoppia completamente, rende lo scambio tipicamente asincrono e consente loro di vivere in processi, linguaggi e deployment diversi. Uno è un design pattern interno a un'app; l'altro è un'architettura fra sistemi.
Che cos'è un topic nel pub/sub?
Un canale logico con un nome che categorizza i messaggi: i publisher si rivolgono al topic e il broker consegna ogni messaggio a tutti i subscriber di quel topic. Il filtraggio può essere per topic (sottoscrizione per nome, spesso gerarchica – orders.*) oppure per contenuto, con il broker che confronta gli attributi dei messaggi.
Quali garanzie di consegna offre il pub/sub?
Tre livelli. At-most-once: fire-and-forget – rapido, qualche messaggio può perdersi. At-least-once: retry fino alla conferma – la scelta predefinita più comune, che produce duplicati occasionali. Exactly-once: deduplicazione più coordinamento – costoso, vincolato e impossibile da garantire da un capo all'altro nel caso generale. La posizione pragmatica: consegna at-least-once con handler idempotenti.
Il pub/sub garantisce l'ordine dei messaggi?
Non per impostazione predefinita – i messaggi partono in fan-out verso subscriber paralleli, e un ordine globale combatte il parallelismo. I broker sanno ordinare dentro una partizione o una chiave di ordinamento, al prezzo del throughput. Una progettazione robusta presuppone un ordine solo dove è configurato in modo esplicito e scrive handler che tollerano gli arrivi fuori sequenza.
Kafka è pub/sub?
Sì, con una sfumatura: Apache Kafka implementa la semantica publish-subscribe sopra un commit log partizionato, durevole e riproducibile (replay). Dentro un consumer group si comporta come una coda (ogni messaggio va a un solo membro); fra gruppi diversi si comporta come pub/sub (ogni gruppo riceve una copia) – ed è per questo che di solito si parla di event streaming.
Quando conviene evitare il pub/sub?
Quando chi chiama ha bisogno di una risposta immediata (richiesta-risposta), quando alcune operazioni devono essere atomiche fra produttore e consumatori (un broker non può partecipare a una transazione – si veda il pattern saga), quando serve un ordine globale stretto, oppure quando il sistema è abbastanza piccolo perché un broker aggiunga più superficie operativa di quanta ne tolga.