Messaggistica 101: queue vs stream vs pub/sub 📬
Se in team avete la frase “mettiamo Kafka” come risposta universale… questa pagina serve a ridurre i danni.
Il punto non è lo strumento: è il modello operativo 🧠
Quando scegli un sistema di messaggistica stai scegliendo:
- come gestisci durabilità e replay;
- che garanzie hai su ordering;
- come fai scalare i consumer;
- come gestisci errori e retry.
Queue (coda) 🧺
flowchart LR
P[Producer] --> Q[(Queue)]
Q --> C[Consumer]
C -->|ack| Q
Modello: messaggi consumati e “rimossi” (concettualmente) dopo l’ack.
- Ottima per: task asincroni, job distribuiti, workload “competing consumers”.
- Attenzioni: DLQ, visibility timeout, retry storm, poison messages.
Stream (log di eventi) 🧾
flowchart LR
P[Producer] --> S[(Stream / Log)]
S --> CG1[Consumer group A]
S --> CG2[Consumer group B]
CG1 -->|offset| S
CG2 -->|offset| S
Modello: append-only log con retention; i consumer mantengono un offset.
- Ottima per: integrazioni multiple, audit/replay, data pipeline, proiezioni (CQRS).
- Attenzioni: partitioning, reprocessing, schema evolution, consumer lag.
Pub/sub 📣
flowchart LR
P[Publisher] --> T[(Topic)]
T --> S1[Subscriber 1]
T --> S2[Subscriber 2]
T --> S3[Subscriber 3]
Modello: publish e fan-out verso subscriber (spesso con filtri).
- Ottima per: notifiche, integrazioni a molti consumer, trigger near real-time.
- Attenzioni: filtri, rate limits, backpressure, governance dei topic.
Cose che confondono sempre (e non dovrebbero) 🧩
Ordering 🧵
Quando qualcuno dice “serve ordering”, la domanda vera è: ordering di cosa, dove, e con quale chiave? Molti broker lo garantiscono, ma con condizioni molto specifiche.
- spesso è garantito solo per chiave/partizione;
- se cambi la chiave di partizionamento a metà progetto… preparati a spiegare perché l’ordine “non è più un diritto costituzionale”.
Consumer group 🧑🤝🧑
È lo strumento base per scalare: più consumer, più throughput. Ma porta con sé concorrenza, race conditions e “perché l’evento è arrivato dopo?” (spoiler: non è arrivato dopo, lo hai processato dopo).
- Stesso gruppo: i consumer si dividono il lavoro — ogni messaggio viene consegnato a uno solo (competing consumer). È il modello di scalabilità orizzontale per distribuire carico.
- Gruppi diversi sullo stesso topic: ogni gruppo riceve tutti i messaggi in modo indipendente (fan-out). I consumer group A e B leggono entrambi l’intero stream senza interferirsi.
Riepilogo:
| Scenario | Comportamento |
|---|---|
| Consumer A1, A2 nello stesso gruppo | Ogni messaggio va a uno solo (competing) |
| Consumer gruppo A + gruppo B sullo stesso topic | Ogni gruppo riceve tutti i messaggi (fan-out) |
- scala orizzontalmente “dividendo” il lavoro;
- richiede progettare bene le chiavi e accettare che più consumer = più concorrenza = più edge cases.
Retention e replay 🔁
Replay è potere (ricostruisci stato, rifai proiezioni, correggi bug), ma è anche un test di maturità: se non sei idempotente, è roulette.
- se puoi fare replay, devi progettare idempotenza;
- se non puoi fare replay, devi investire ancora di più su affidabilità e su come gestisci gli errori.
Criteri pratici per scegliere 🎛️
Chiediti:
- Ti serve replay per ricostruire stato/proiezioni? → stream.
- Ti serve distribuire job e non ti interessa la storia completa? → queue.
- Ti serve fan-out “pulito” a molti consumer? → pub/sub (o stream con più consumer group).
- Hai vincoli forti di ordering? → controlla come funziona davvero nel broker scelto.
Tre capacità spesso sottovalutate 🛠️
Nei confronti “broker A vs broker B”, spesso si guarda solo throughput e prezzo. Poi arrivano i problemi veri e scopri che mancavano tre domande fondamentali:
- Filtering: il broker può filtrare eventi lato infrastruttura o stai scaricando tutto lato consumer? Se supporta filtri server-side (es. per header o attributi), risparmi bandwidth e CPU; altrimenti implementa filtri nel consumer e monitora gli scarti.
- Backpressure/flow control: come rallenti producer o consumer quando il sistema satura? Preferisci meccanismi che fanno cadere la pressione verso il producer (rate limiting, pause) invece di retry incontrollati; a volte un buffer temporaneo con shed policy è più pratico.
- Guaranteed delivery (pratica, non marketing): quali garanzie hai davvero su persistenza, ack, retry e duplicati? Verifica casi pratici (network partition, broker crash) e definisci cosa succede a livello applicativo: non accettare claim marketing senza test di recovery.
Queste tre cose non sono “nice to have”: spesso determinano costi operativi, latenza e serenità on-call.
Publish/subscribe, point-to-point, request/reply 📮
Queste tre etichette descrivono come viaggiano i messaggi — indipendentemente da quale broker usi.
- Publish/subscribe: un evento viene consegnato a tutti i subscriber interessati (fan-out). Corrisponde al modello di più consumer group sullo stesso topic.
- Point-to-point: un messaggio va a un singolo destinatario (competing consumer). Corrisponde al modello di più consumer nello stesso gruppo su una queue o partizione.
- Request/reply: un producer aspetta una risposta da un recipient specifico. In EDA è utile quando serve una risposta deterministica, ma attenzione: se stai solo spostando sincronia sotto un tappeto di code, prima o poi inciampi.
Regola pratica: se stai ricreando request/response con una coda, molto probabilmente quello che cerchi è una chiamata HTTP/gRPC diretta.
Checklist di adozione (minima ma onesta) ✅
Questa non è burocrazia: è assicurazione. Senza queste decisioni esplicite, la piattaforma finisce in modalità “ognuno fa come gli pare” in circa due sprint.
- Definisci naming per topic/stream.
- Definisci ownership (chi è responsabile del canale).
- Definisci policy: retention, DLQ, retry.
- Definisci metriche: throughput, error rate, consumer lag.
Prossimi passi 🚀
Se ti è chiaro come si muovono i messaggi, il passo successivo è progettare cosa contengono e come li rendi operabili.
- Per progettare payload e schema: vedi la guida su event design.
- Per idempotenza, retry e DLQ: vedi la guida operativa.