DIEGO CECATO / NOTE DAL CAMPO

Symfony 8.2 e MongoDB: meno servizi, più responsabilità

diego cecato//
Symfony 8.2 e MongoDB: meno servizi, più responsabilità

La terza infrastruttura è sempre quella che nessuno voleva mantenere

Un’applicazione usa MongoDB per i dati, Redis per la cache e un broker separato per i messaggi. Sulla carta è una scelta ordinata: ogni componente fa una cosa e la fa bene. Nella realtà significa tre sistemi da aggiornare, monitorare, mettere in sicurezza, salvare, ripristinare e capire quando qualcosa smette di funzionare. È qui che una novità apparentemente piccola di Symfony 8.2 diventa interessante: non perché MongoDB sia improvvisamente il miglior sistema di cache o di messaggistica, ma perché rende possibile ridurre il numero di componenti quando il contesto lo giustifica.

Il 23 ottobre 2026 il blog ufficiale di Symfony ha presentato nuovi adapter MongoDB per Cache e un trasporto MongoDB per Messenger. Sono funzionalità annunciate per Symfony 8.2, non un invito a migrare tutto senza prove. E soprattutto non sono un benchmark contro Redis o RabbitMQ.

La domanda utile non è “Posso farlo?”. La domanda è: “Quale complessità sto eliminando e quale sto spostando sul database?”. Chi misura solo il numero di servizi sul diagramma rischia di perdere proprio la parte che dovrebbe progettare.

Cosa cambia davvero in Symfony 8.2

Cache: documenti, tag e scadenze

Il componente Cache introduce MongoDbAdapter e MongoDbTagAwareAdapter. Ogni elemento della cache diventa un documento MongoDB. Nella variante tag-aware i tag sono memorizzati nello stesso documento e l’invalidazione di un tag può essere eseguita con una singola operazione di cancellazione. Per un’applicazione che usa già MongoDB, un pool può essere configurato con un DSN mongodb:// o mongodb+srv://, dopo aver installato la libreria mongodb/mongodb.

La presenza di un indice TTL permette a MongoDB di eliminare gli elementi scaduti. Attenzione alla distinzione: un elemento logicamente scaduto non deve essere considerato valido, ma la sua rimozione fisica tramite TTL non è necessariamente istantanea. Se la tua applicazione confonde queste due cose, non è l’adapter a dover correggere il modello mentale.

Messenger: la coda è una collection, non un array

Con il pacchetto symfony/mongodb-messenger, Messenger può memorizzare i messaggi in una collection MongoDB. Il trasporto prevede l’acquisizione atomica del messaggio da parte di un consumer, così due worker non dovrebbero prendere contemporaneamente lo stesso lavoro. Se un worker si interrompe, un messaggio può tornare disponibile dopo redeliver_timeout, impostato per default a un’ora. Il comando messenger:setup-transports prepara l’indice necessario a individuare i messaggi in attesa.

Qui compare una distinzione che continua a essere ignorata: l’acquisizione atomica riduce il rischio di doppio claim nello stesso momento, non garantisce che il lavoro venga eseguito una volta sola in assoluto. Un consumer può completare un’operazione e morire prima di registrare l’esito; il messaggio verrà consegnato di nuovo. Se il tuo handler addebita due volte un ordine o invia due email perché è stato richiamato due volte, non è un problema risolto dal nome del trasporto.

Avevo già affrontato il tema dei confini di fiducia in Symfony Messenger: la coda non è un confine di fiducia. Qui il punto è complementare: una coda affidabile richiede idempotenza, osservabilità, politiche di retry e un modo per capire che cosa è stato davvero elaborato.

Una transazione può tenere insieme dato ed evento

Symfony introduce anche MongoDbSessionStamp. Passando la sessione di una transazione MongoDB a Messenger, è possibile salvare un documento applicativo e inviare il messaggio corrispondente nella stessa transazione. È un passo importante per il problema del dual-write: l’ordine viene registrato ma il relativo evento non parte, oppure parte l’evento e poi fallisce la scrittura dell’ordine.

Non significa però che il sistema diventi magicamente esattamente-once. La transazione riguarda le scritture che partecipano davvero alla stessa sessione e richiede un deployment MongoDB con supporto alle transazioni. Non comprende automaticamente gli effetti esterni dell’handler: API di pagamento, email, sistemi terzi. Per quelli servono ancora chiavi di idempotenza, riconciliazione e gestione esplicita dei fallimenti.

Più code in una richiesta, ma senza priorità implicite

Un worker può consumare più code presenti nella stessa collection usando una sola richiesta al server. È una riduzione concreta del lavoro di polling, soprattutto se il sistema ha diverse code con traffico intermittente. Ma la documentazione chiarisce anche il limite: la selezione di più code non stabilisce una gerarchia di priorità. Se il tuo business distingue ordini urgenti da elaborazioni differibili, devi progettare quella politica invece di presumere che l’ordine dei nomi nella configurazione la garantisca.

Meno componenti non significa meno failure mode

Mettere cache e messaggi nello stesso database che contiene lo stato applicativo cambia la distribuzione del rischio. Riduci un servizio da amministrare, forse due, ma concentri su MongoDB traffici con caratteristiche molto diverse: letture veloci e spesso sacrificabili, scritture applicative da preservare, claim concorrenti e code che possono accumularsi durante un incidente.

Il costo operativo di un’istanza Redis non è identico al costo di un cluster MongoDB più carico. E il numero di container non misura né la latenza di coda né il tempo di recupero dopo un guasto. Senza metriche su saturazione, dimensione delle collection, ritardi di consegna, pressione sugli indici e comportamento al riavvio, dire che l’architettura è diventata “più semplice” resta una dichiarazione estetica.

Eliminare un componente può semplificare il sistema. Concentrare tutte le responsabilità su quello rimasto può renderlo più fragile. La differenza la fanno i test, non il diagramma.

C’è anche una questione di isolamento. Se un picco di job satura risorse condivise, può degradare le query degli utenti. Se una campagna di invalidazione produce molte cancellazioni, devi conoscere il suo impatto sulle altre operazioni. E se la disponibilità della coda dipende dalla disponibilità dello stesso database che deve essere ripristinato, il piano di disaster recovery deve considerare entrambe le funzioni, non soltanto i documenti di business.

Quando scegliere MongoDB e quando tenere servizi dedicati

Usare MongoDB per cache e Messenger può essere sensato quando l’applicazione lo utilizza già, il carico è moderato o prevedibile, il team conosce bene il database e l’obiettivo è ridurre costi di esercizio senza perdere requisiti essenziali. È particolarmente interessante per servizi interni, progetti con poche code e team piccoli, dove aggiungere una tecnologia significa anche aggiungere competenze e procedure che nessuno ha tempo di mantenere.

Redis resta una scelta da valutare quando servono pattern di caching molto intensi, isolamento delle latenze o strutture dati specifiche. Un broker dedicato come RabbitMQ può essere preferibile quando routing, priorità, backpressure, topologie di consegna o operatività delle code diventano esigenze centrali. Non perché sia “più enterprise”, ma perché offre meccanismi progettati per quel lavoro. La scelta va fatta sul comportamento richiesto, non sul logo.

Prima di cambiare architettura farei almeno quattro prove: un carico realistico con cache e code insieme, il crash di un worker a metà elaborazione, la saturazione o indisponibilità temporanea di MongoDB e la riconciliazione dopo un redelivery. Non servono slide particolarmente elaborate: servono tempi misurati, duplicazioni rilevate e un criterio per dire se il sistema è rientrato in uno stato coerente.

È lo stesso motivo per cui un lock non rende automaticamente atomica un’operazione, come spiegato in Symfony 8.2: un attributo non rende atomica un’operazione. Le primitive sono utili, ma il loro perimetro va compreso. Un lock, una transazione o un claim non possono farsi carico di tutto ciò che succede prima e dopo.

La scelta architetturale non è un concorso di tecnologie

Le integrazioni MongoDB di Symfony 8.2 sono una buona notizia proprio perché ampliano le opzioni, non perché decretano un vincitore. Rendono più accessibile una configurazione con meno dipendenze, ma lasciano intatti i problemi che distinguono un sistema dimostrativo da uno che deve funzionare il lunedì mattina: retry, duplicati, osservabilità, backup, failover e carico concorrente.

Aggiungere strumenti senza una strategia produce frammentazione. Toglierli senza capire quali responsabilità svolgevano può produrre lo stesso risultato, con meno icone sulla dashboard. La domanda finale non è quanti servizi sei riuscito a eliminare, ma se adesso sai spiegare meglio dove si trova ogni responsabilità, come si comporta quando qualcosa va storto e chi se ne accorge in tempo.

Domande frequenti

Symfony 8.2 sostituisce Redis e RabbitMQ con MongoDB?

No. Aggiunge adapter per la cache e un trasporto Messenger basati su MongoDB. Puoi usarli se rispondono ai requisiti del progetto, ma non sostituiscono automaticamente funzionalità, prestazioni e politiche operative dei servizi dedicati.

Il trasporto MongoDB di Messenger garantisce che ogni messaggio venga eseguito una sola volta?

No. Il claim atomico evita che due worker prendano simultaneamente lo stesso messaggio, ma dopo un crash è previsto il redelivery. Gli handler devono quindi gestire duplicati e idempotenza.

A cosa serve MongoDbSessionStamp?

Permette di passare a Messenger la sessione di una transazione MongoDB, in modo che una scrittura applicativa e l’invio del messaggio partecipino alla stessa transazione. Non rende atomici effetti esterni come pagamenti o email.

Un worker Symfony può assegnare priorità alle code MongoDB consumate insieme?

La possibilità di consumare più code nella stessa collection con una richiesta non introduce automaticamente priorità. Se servono precedenze o isolamento tra workload, occorre progettarli esplicitamente.