Il modello Strangler Fig: un modo sicuro per migrare applicazioni monolitiche
Nella moderna ingegneria del software, le applicazioni monolitiche legacy rappresentano una sfida comune. Nel corso del tempo, una codebase di successo diventa così grande e interconnessa che apportare semplici modifiche diventa rischioso, le implementazioni richiedono ore e il ridimensionamento delle singole funzionalità è praticamente impossibile.
Quando i team decidono di modernizzare i propri sistemi migrando ai microservizi, si trovano ad affrontare una domanda ad alto rischio: Come possiamo riscrivere il sistema senza compromettere la nostra attività attuale?
Un’opzione è una riscrittura “Big Bang”: costruire il nuovo sistema da zero a porte chiuse e cambiare tutto in un solo giorno. Tuttavia, questo è incredibilmente rischioso e spesso porta al fallimento.
Fortunatamente, esiste un’alternativa più sicura e affidabile: il modello di fico strangolatore. In questa guida esploreremo cos’è lo Strangler Fig Pattern, perché funziona e come applicarlo passo dopo passo utilizzando diagrammi chiari e codice reale.
L’analogia con il mondo reale: la pianta del fico strangolatore
Il motivo prende il nome dal fico strangolatore, una pianta originaria delle foreste pluviali tropicali.
Un seme di fico strangolatore germina nei rami superiori di un albero “ospite” esistente. Invece di crescere da terra verso l’alto, il fico cresce verso il basso:
- Invia le radici lungo il tronco dell’albero ospite finché non raggiungono il suolo della foresta e si ancorano al terreno.
- Nel corso del tempo, crescono più radici, si avvolgono attorno all’ospite e si fondono insieme.
- Sul fico crescono foglie che impediscono alla luce di raggiungere l’albero ospite.
- Alla fine, l’albero ospite muore e marcisce, lasciando al suo posto un fico strangolatore cavo.
Nell’architettura software, il monolite legacy è l’albero host e i nuovi microservizi sono il fico strangolatore. Costruiamo i nuovi servizi attorno ai bordi del monolite, spostando gradualmente il traffico dal sistema legacy fino a quando il monolite non potrà essere completamente chiuso.
Perché le riscritture del “Big Bang” falliscono
Prima di immergerci nei meccanismi dello schema del Fico Strangolatore, capiamo perché l’alternativa, una riscrittura completa, è così pericolosa:
- Nessun valore per mesi (o anni): gli sviluppatori dedicano molto tempo alla scrittura del codice, ma nulla di ciò viene pubblicato finché l’intero progetto non viene completato.
- Scope Creep: durante una riscrittura di due anni, le esigenze aziendali cambiano. Il target si sposta e il nuovo sistema deve supportare funzionalità che non esistevano quando è iniziata la riscrittura.
- Comportamento implicito mancante: i monoliti contengono anni di correzioni di bug non documentate e gestioni di casi limite. Una riscrittura pulita spesso dimentica questi dettagli.
- Elevato rischio di implementazione: disattivare un enorme sistema legacy e attivarne uno nuovo tutto in una volta crea un raggio di esplosione enorme se qualcosa va storto.
Come funziona il modello del fico strangolatore
L’idea centrale del modello Strangler Fig è la migrazione incrementale. Invece di migrare l’intero sistema, si esegue la migrazione di una piccola funzionalità o “sezione” alla volta.
Il processo di migrazione viene eseguito in cinque fasi chiave:
1. Identificare un contesto delimitato
Osserva il tuo monolite e identifica un’unica capacità aziendale autonoma e facile da estrarre. I buoni candidati includono:
- Funzionalità che cambiano frequentemente (quindi il team trae rapidamente vantaggio dalle implementazioni indipendenti).
- Funzionalità semplici e a basso rischio (come una sezione FAQ statica o una sezione delle preferenze dell’utente) per testare la pipeline di migrazione.
- Funzionalità con confini del database puliti e ben definiti.
2. Implementare il nuovo microservizio
Costruisci la funzionalità identificata come un microservizio nuovo e moderno. Questo servizio ha un proprio database, una propria pipeline di distribuzione ed è costruito utilizzando stack tecnologici moderni. Fondamentalmente, la vecchia funzionalità del monolite rimane attiva e invariata per ora.
3. Introdurre il livello di intercettazione
Per rendere la migrazione trasparente per i tuoi utenti, introduci un livello di intercettazione (come un gateway API o un proxy inverso) davanti all’applicazione. Tutto il traffico client ora va prima a questo gateway.
Inizialmente, il gateway instrada il 100% di tutte le richieste al monolite legacy.
4. Transizione incrementale del traffico
Una volta che il nuovo microservizio è completamente testato e pronto, aggiorni le regole di instradamento nel livello di intercettazione. Invece di instradare le richieste per la funzionalità migrata (ad esempio, /api/users) al monolite, il gateway le reindirizza al nuovo microservizio.
Tutte le altre richieste continuano ad andare al monolite. Se il nuovo servizio fallisce o presenta bug, puoi aggiornare rapidamente il gateway per instradare il traffico al monolite, garantendo interruzioni minime.
5. Disattivazione e ripetizione
Dopo che il nuovo microservizio viene eseguito stabilmente per un periodo di tempo, puoi eliminare in sicurezza il codice corrispondente all’interno del monolite legacy.
Quindi seleziona la funzione successiva e ripeti il processo. Nel corso del tempo, il monolite si restringe fino a esaurire il traffico rimanente ed è possibile disattivare completamente il server legacy.
Il livello di intercettazione: esempio di routing rapido
Il cuore del pattern Strangler Fig è lo Interception Layer. Consente di reindirizzare le richieste senza modificare le applicazioni client (app Web, app mobili).
Ecco un esempio pratico di Node.js che utilizza un proxy gateway basato su Express. Instrada le richieste in modo dinamico: le richieste in entrata vanno ai nuovi servizi se corrispondono ai percorsi migrati; in caso contrario, ricadono nel monolite ereditato.
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
const PORT = 8080;
// Configuration: Target server URLs
const LEGACY_MONOLITH_URL = 'http://legacy-monolith-server:3000';
const NEW_USER_SERVICE_URL = 'http://new-user-service:3001';
const NEW_PAYMENT_SERVICE_URL = 'http://new-payment-service:3002';
// Simple logging middleware to track traffic distribution
app.use((req, res, next) => {
console.log(`[ROUTE LOG] Incoming request: ${req.method} ${req.url}`);
next();
});
// 1. MIGRATED: User registration & profile requests route to the new microservice
app.use('/api/users', createProxyMiddleware({
target: NEW_USER_SERVICE_URL,
changeOrigin: true,
pathRewrite: {
'^/api/users': '/v1/users', // Translate path format if necessary
}
}));
// 2. MIGRATED: Payment transactions route to the new payment microservice
app.use('/api/payments', createProxyMiddleware({
target: NEW_PAYMENT_SERVICE_URL,
changeOrigin: true
}));
// 3. FALLBACK: All other legacy routes automatically default to the Monolith
app.use('/', createProxyMiddleware({
target: LEGACY_MONOLITH_URL,
changeOrigin: true
}));
app.listen(PORT, () => {
console.log(`Interception Gateway routing traffic successfully on port ${PORT}`);
});
Gestire il database: la parte più difficile
Sebbene l’instradamento del traffico API sia relativamente semplice, la gestione dei dati rappresenta l’aspetto più impegnativo della migrazione monolitica. Un monolite di solito ha un unico, enorme database in cui le tabelle sono altamente unite.
Quando estrai un servizio, devi anche estrarne i dati. Esistono due approcci comuni per gestire questo problema:
- Doppie scritture: il livello di intercettazione o l’applicazione scrive i dati sia nel database legacy che nel nuovo database del servizio contemporaneamente durante la fase di transizione. Ciò mantiene entrambi i database sincronizzati.
- Change Data Capture (CDC): uno strumento come Debezium monitora il registro delle transazioni del database legacy e trasmette automaticamente le modifiche al nuovo database dei microservizi quasi in tempo reale.
Una volta che sei sicuro che i database siano completamente sincronizzati e che il nuovo database sia corretto, trasferisci il traffico di lettura al nuovo servizio e chiudi le tabelle legacy.
Confronto: Big Bang Rewrite vs. Strangler Fig
| Caratteristica/metrica | Riscrittura del Big Bang | Modello di fico strangolatore |
|---|---|---|
| Livello di rischio | Estremamente alto | Basso e gestito |
| Ciclo di feedback | Molto lento (solo alla fine) | Veloce (test di produzione continuo) |
| Strategia di ripristino | Difficile (richiede il ripristino dei backup) | Facile (modifica le regole del percorso nel gateway) |
| Impatto aziendale | Dirompente | Zero tempi di inattività |
| Complessità del sistema | Alto (durante la fase di costruzione) | Elevato (durante la fase di migrazione) |
| Tempo di distribuzione | Enorme rilascio del singolo | Aggiornamenti piccoli e frequenti |
Conclusione
Lo Strangler Fig Pattern è lo standard di settore per la migrazione dei monoliti ai microservizi. Sostituendo il codice legacy in modo incrementale anziché tutto in una volta, si elimina il rischio di un errore “Big Bang”.
Mantiene piccole le distribuzioni, fornisce feedback immediato dal traffico di produzione reale e consente al tuo team di continuare a fornire valore aziendale durante l’intero ciclo di vita della migrazione.
Sebbene la gestione della migrazione del database e l’esecuzione di due sistemi paralleli aggiungano complessità operativa, la sicurezza, la prevedibilità e la stabilità che offre lo rendono la scelta preferita per le moderne migrazioni cloud.