Node.js e TypeScript per microservizi: quando PHP non basta
Il monolite PHP regge benissimo la stragrande maggioranza dei casi, e chi ti dice il contrario sta vendendo complessità. Ma esistono carichi specifici per cui un microservizio in Node.js e TypeScript non è una moda, è la scelta tecnica corretta, perché tocca esattamente il punto in cui il modello di esecuzione di PHP smette di essere il più adatto. Parlo del real-time, delle connessioni persistenti, dell'I/O intensivo e concorrente: scenari in cui il modo in cui Node gestisce le connessioni batte, per architettura, il modo in cui le gestisce PHP. La decisione di affiancare un servizio Node a un backend PHP, però, va presa per la ragione giusta, non perché "i microservizi sono moderni", perché frammentare un'architettura senza un motivo reale è uno dei modi più efficaci per moltiplicare i problemi. In un progetto con un team internazionale ho costruito proprio questo tipo di affiancamento, un adattatore Node/TypeScript accanto a un cuore PHP, e la lezione è che funziona se i confini sono netti e fallisce se sono confusi. Vediamo dove Node è davvero la risposta, dove invece è solo frammentazione, e come far convivere i due mondi senza duplicare la logica di business.
Il modello di esecuzione: dove PHP e Node divergono davvero
Per decidere bene bisogna capire la differenza di fondo, che è architetturale e spiega tutto il resto. PHP, nel suo modello classico, segue lo schema una richiesta, un processo, poi muore: ogni richiesta HTTP parte pulita, fa il suo lavoro, restituisce la risposta e il processo si libera. È un modello shared-nothing meraviglioso per il web tradizionale, perché è semplice, robusto e isola ogni richiesta dalle altre. Ma è pensato per richieste brevi e indipendenti, non per tenere aperte migliaia di connessioni contemporaneamente.
Node ha un modello opposto: un processo persistente con un event loop a singolo thread e I/O non bloccante. Invece di un processo per richiesta, Node gestisce moltissime connessioni concorrenti in un solo processo, perché quando una connessione aspetta un'operazione di I/O (una query, una chiamata di rete) non blocca le altre, ma cede il controllo all'event loop che serve nel frattempo qualcun altro. Questo lo rende eccellente per gli scenari con molte connessioni che restano aperte e fanno poco lavoro ciascuna, e lo penalizza invece sui calcoli pesanti che occupano la CPU, perché il singolo thread resta bloccato. La divergenza non è "uno è meglio dell'altro": è che sono ottimizzati per forme di carico diverse.
Chiedere a PHP di tenere mille connessioni WebSocket aperte è come chiedere a un casello autostradale di ospitare le auto per la notte: è progettato per farle passare in fretta, una dopo l'altra, non per tenerle ferme tutte insieme. Node è il parcheggio; PHP è il casello. Servono a cose diverse.
La tabella: quale carico a quale runtime
Messa giù in modo decisionale, la scelta si chiarisce guardando la natura del carico:
| Tipo di carico | Runtime più adatto | Perché |
|---|---|---|
| Web classico, CRUD, logica di business | PHP | Richieste brevi, shared-nothing, ecosistema maturo |
| Real-time, WebSocket, notifiche live | Node.js | Connessioni persistenti gestite nell'event loop |
| I/O concorrente intensivo (molte chiamate esterne) | Node.js | I/O non bloccante, molte connessioni in un processo |
| Streaming di dati verso molti client | Node.js | Connessioni aperte e leggere, nativamente |
| Calcolo pesante CPU-bound | Nessuno dei due ideale | Single-thread soffre; meglio worker dedicati |
| Elaborazione dati, calcolo scientifico | (Python) | Ecosistema dedicato |
La tabella dice una cosa precisa: PHP resta la scelta di default per il cuore dell'applicazione, e Node entra in gioco per una famiglia specifica di carichi, quelli imperniati sulle connessioni persistenti e sull'I/O concorrente. Fuori da quella famiglia, aggiungere Node è quasi sempre un costo senza un guadagno proporzionato.
Se hai un'applicazione PHP e un requisito che sembra chiedere il real-time o l'alta concorrenza, e vuoi capire se vale la pena introdurre Node, nel mio profilo professionale trovi l'esperienza concreta su architetture multi-stack dove la scelta del runtime è motivata dal carico, non dalla moda.
Il caso d'uso che chiede Node: il real-time
L'esempio più nitido in cui PHP mostra il fianco e Node brilla è il real-time. Pensa a una dashboard che si aggiorna in tempo reale, a un sistema di notifiche istantanee, a una chat, allo streaming di eventi verso molti utenti collegati insieme. Tutti questi scenari richiedono connessioni persistenti, tipicamente via WebSocket, che restano aperte per minuti o ore. Il modello a processo per richiesta di PHP è strutturalmente inadatto a tenere migliaia di connessioni aperte, perché ogni connessione persistente immobilizzerebbe un processo: con mille utenti collegati, mille processi fermi ad aspettare.
Node gestisce lo stesso scenario tenendo le mille connessioni nell'event loop di un singolo processo, ciascuna che consuma pochissimo finché non c'è qualcosa da inviare. È la differenza tra il poter fare e il non poter fare, non tra il fare meglio o peggio. Per questo, quando un'applicazione PHP ha bisogno di una funzionalità real-time seria, la soluzione pulita è un servizio Node dedicato a quella funzionalità, accanto al monolite, come ho descritto parlando di architetture real-time in Node.js e TypeScript con streaming. Il monolite continua a fare l'applicazione, il servizio Node fa il real-time, e ciascuno fa ciò per cui è tagliato.
Perché TypeScript, e non JavaScript nudo
Una precisazione che per un senior non è negoziabile: se introduci Node in un sistema serio, lo fai in TypeScript, non in JavaScript puro. La ragione è la manutenibilità. Un servizio che fa parte di un sistema più grande, che scambia dati con un backend PHP e che evolverà nel tempo, ha bisogno della disciplina dei tipi: il compilatore che cattura un'intera classe di errori prima che arrivino in produzione, i contratti dei dati espliciti e verificati, l'editor che capisce la struttura del codice e ti aiuta a non sbagliare. JavaScript senza tipi va bene per uno script di poche righe; per un servizio che deve durare e integrarsi, l'assenza di tipi è debito tecnico dal primo giorno. TypeScript costa un po' di disciplina in più in scrittura e la ripaga moltiplicata in ogni modifica successiva, esattamente come la tipizzazione stretta in PHP moderno.
Quando estrarre un servizio frammenta invece di aiutare
Qui sta la disciplina che separa l'architettura dalla moda. I microservizi hanno un costo reale e spesso sottovalutato: introducono la rete tra componenti che prima si chiamavano in memoria, aggiungono complessità di deploy e di orchestrazione, rendono il debugging distribuito e più difficile, e portano il problema della consistenza tra servizi. Estrarre un servizio per moda, perché "il monolite è superato", significa accollarsi tutti questi costi senza il beneficio che li giustifica.
La regola che applico è estrarre un servizio solo quando c'è una ragione tecnica genuina (un carico, come il real-time, che il monolite non gestisce bene) oppure una ragione organizzativa genuina (un team separato, un ciclo di rilascio indipendente, una scalabilità che serve solo a quel pezzo). Fuori da queste ragioni, il monolite ben fatto resta la scelta migliore per la maggior parte delle applicazioni, ed è un consiglio che ho ribadito parlando di quando i microservizi su PHP e Symfony valgono la complessità. Il "majestic monolith" affiancato da pochi servizi mirati, ciascuno con la sua ragion d'essere, batte quasi sempre la nuvola di microservizi che va di moda nei convegni e crea incubi in produzione.
I confini per non duplicare la logica di business
Il rischio numero uno di un'architettura multi-runtime è la duplicazione della logica di business. Se le regole del dominio finiscono sia nel PHP che nel Node, hai due fonti di verità che prima o poi divergeranno, e una regola corretta a metà è peggio di una regola sbagliata. La difesa è tenere una sola fonte di verità e dare al servizio Node un ruolo specialista, non quello di una seconda copia del dominio. Il servizio Node è un gateway real-time, un backend for frontend, un adattatore per uno specifico canale: fa il suo mestiere tecnico e per le decisioni di business si appoggia al cuore PHP, invece di reimplementarle.
Questo pattern, in cui un servizio Node fa da strato dedicato verso il frontend o verso un canale specifico senza possedere la logica di dominio, è il backend for frontend, che ho descritto parlando del pattern BFF in Node.js. Tenere la logica di business in un posto solo, e usare Node per ciò che è puramente tecnico, è ciò che impedisce all'architettura multi-stack di degenerare in due applicazioni che si contraddicono.
Come convivono i due mondi, in pratica
Stabiliti i confini, la convivenza è una questione di contratti chiari. I due runtime comunicano attraverso un'interfaccia definita: un'API tra il monolite e il servizio, una coda di messaggi per i flussi asincroni, o un accesso condiviso ai dati gestito con disciplina perché la fonte di verità resti una. L'autenticazione si condivide tipicamente con un token firmato che entrambi sanno verificare, così l'utente è lo stesso attraverso i due mondi senza che ciascuno reimplementi l'identità. La regola, di nuovo, è il contratto esplicito: il formato dei dati scambiati va definito e versionato, perché il confine tra due runtime è anche un confine tra due basi di codice che evolvono separatamente, ed è lì che le integrazioni si rompono se nessuno presidia la compatibilità. Trattato così, il passaggio tra PHP e Node è netto e prevedibile; trattato con superficialità, diventa la fonte dei bug più difficili da diagnosticare, quelli che vivono nello spazio tra due sistemi.
C'è poi la faccia operativa, che va messa in conto come per qualunque servizio aggiuntivo. Un servizio Node è un processo persistente, e a differenza di una richiesta PHP che nasce e muore, deve restare in piedi, il che significa gestirlo con un process manager (un PM2 o un'unit di systemd) che lo riavvii se cade e lo tenga sotto controllo. Va monitorato in modo specifico: un event loop bloccato da un'operazione troppo pesante è il modo tipico in cui un servizio Node degrada, e va sorvegliato il ritardo dell'event loop come metrica di salute, perché un suo aumento è il primo segnale che qualcosa sta saturando il singolo thread. Sono accortezze diverse da quelle di un'applicazione PHP, e vanno padroneggiate prima di mettere il servizio in produzione, non scoperte dopo il primo blocco.
Devo passare ai microservizi?
No, quasi mai in blocco, ed è la risposta che fa risparmiare più soldi di tutte. Passare un'applicazione monolitica funzionante a un'architettura a microservizi "perché è il futuro" è uno dei progetti più costosi e rischiosi che una PMI possa intraprendere, e il più delle volte non risolve un problema reale, ne crea di nuovi. La strada giusta è l'opposto: si parte e si resta sul monolite finché regge, e si estrae un servizio solo quando un carico specifico lo richiede. Quel servizio Node per il real-time non è "il primo passo verso i microservizi", è la risposta mirata a un'esigenza precisa, e va bene che resti l'unico se non ce ne sono altre di altrettanto motivate. L'architettura giusta non è quella con più servizi, è quella con il numero minimo di pezzi che risolve i problemi reali, perché ogni pezzo in più è qualcosa che qualcuno dovrà capire, deployare e tenere in vita per gli anni a venire.
Tirando le somme, Node.js e TypeScript sono un alleato prezioso di un backend PHP esattamente come lo è Python, ma per carichi diversi: dove Python eccelle sui dati e sul calcolo, Node eccelle sulle connessioni persistenti, sul real-time e sull'I/O concorrente, grazie a un modello di esecuzione costruito per quello. La decisione di introdurlo va presa guardando la natura del carico, non la moda dei microservizi: si estrae un servizio quando c'è una ragione tecnica o organizzativa vera, si tiene TypeScript per la disciplina dei tipi, si danno al servizio confini netti e un ruolo da specialista per non duplicare la logica, e si fanno comunicare i due mondi con contratti espliciti. Fatto così, il monolite PHP resta il cuore solido dell'applicazione e Node ne estende le capacità proprio dove servono, senza frammentare nulla. Fatto per moda, è il modo più rapido per trasformare un'applicazione che funziona in una nuvola di servizi che nessuno controlla. Se hai un'applicazione PHP e un requisito real-time o ad alta concorrenza che ti chiede qualcosa che il monolite fatica a dare, e vuoi capire se un servizio Node è la risposta giusta o una complicazione inutile, contattami per una consulenza diretta: nella mia esperienza, la decisione corretta nasce sempre dalla natura concreta del carico da gestire, mai dalla voglia di adottare l'architettura di cui tutti parlano in quel momento.