Branch by Abstraction: Il Modo Sicuro per Sostituire un Modulo in Produzione
Come usare branch by abstraction per sostituire un modulo in produzione in modo incrementale, con zero downtime e possibilità di rollback in ogni momento.
In questo articolo:
- Il problema che branch by abstraction risolve
- Le quattro fasi di branch by abstraction
- Parallel run: verificare la nuova implementazione
- Quando usare branch by abstraction vs. strangler fig
- Conclusione
Branch by abstraction è una tecnica per sostituire un componente in un sistema in esecuzione senza branch long-lived, senza release big-bang e senza downtime. L’idea è semplice: introduce un livello di astrazione che si frappone tra i chiamanti e il componente da sostituire, poi costruisci la nuova implementazione dietro quell’astrazione, commuta gradualmente, e rimuovi la vecchia implementazione una volta completata la migrazione.
Questo approccio permette ai team di sostituire un modulo in produzione in modo incrementale, consegnando in produzione durante la migrazione piuttosto che dopo. La possibilità di rollback in qualsiasi momento, fino a un singolo toggle di feature flag, è ciò che lo rende adatto ai sistemi di produzione dove la disponibilità non è negoziabile.
Il problema che branch by abstraction risolve
L’approccio tradizionale alla sostituzione di un modulo è: crea un branch, riscrivi il modulo, mergia, testa, distribuisci. Questo ha tre problemi.
Primo, i branch long-lived divergono. Un branch di due settimane in un codebase attivo accumula conflitti. Un branch di due mesi diventa un evento di merge che consuma una settimana ingegneristica e introduce bug sottili che non erano presenti né nell’originale né nel nuovo codice.
Secondo, le release big-bang concentrano il rischio. Tutto il rischio della migrazione si realizza nel momento del deployment. Se qualcosa va storto, lo scopri in produzione, non durante la validazione incrementale.
Terzo, lo sviluppo parallelo si ferma. Mentre esiste il branch di riscrittura, il team non può consegnare funzionalità nell’area in fase di riscrittura senza duplicare l’effort su due versioni.
Branch by abstraction elimina tutti e tre i problemi. Non c’è branch long-lived: la nuova implementazione è costruita sul branch principale dietro un flag. Non c’è release big-bang: il traffico commuta gradualmente. Non c’è blocco dello sviluppo parallelo.
Le quattro fasi di branch by abstraction
Fase 1: Introduci l’astrazione. Crea un’interfaccia o una classe astratta che cattura il contratto del modulo da sostituire. Aggiorna tutti i chiamanti per usare l’astrazione piuttosto che l’implementazione concreta. L’implementazione esistente diventa la prima implementazione concreta della nuova astrazione.
A questo punto, nulla è cambiato funzionalmente. Tutti i chiamanti raggiungono ancora lo stesso codice attraverso il nuovo livello di astrazione. Questa fase dovrebbe essere una PR autonoma facile da revisionare e distribuire.
Fase 2: Costruisci la nuova implementazione. Crea una seconda implementazione concreta dell’astrazione. Questa è il nuovo codice verso cui stai migrando. Costruiscila dietro l’astrazione così può essere sviluppata e testata indipendentemente. La vecchia implementazione è ancora in produzione; la nuova non viene ancora chiamata da nessun traffico di produzione.
Fase 3: Commuta il traffico in modo incrementale. Introduci un feature flag che controlla quale implementazione l’astrazione instrada. Inizia con lo 0% del traffico verso la nuova implementazione, poi aumenta: 1%, 5%, 10%, 25%, 50%, 100%. Ad ogni passo, monitora i tassi di errore, la latenza e le metriche di business. Se qualcosa degrada, riporta il flag indietro.
Fase 4: Rimuovi la vecchia implementazione. Una volta che il 100% del traffico è sulla nuova implementazione ed è stata stabile per un periodo concordato, rimuovi la vecchia implementazione e il feature flag.
Parallel run: verificare la nuova implementazione
Il parallel run pattern è una tecnica di verifica spesso usata insieme a branch by abstraction. Sia la vecchia che la nuova implementazione vengono chiamate con gli stessi input. Gli output vengono confrontati. Le discrepanze vengono registrate.
Questo è particolarmente prezioso quando la vecchia implementazione ha comportamenti non documentati o casi limite non coperti dai test. Eseguire entrambe in parallelo attraverso il traffico di produzione rivela divergenze che nessuna suite di test catturerebbe.
Implementazione: avvolgi entrambe le chiamate nel livello di astrazione. Chiama la vecchia, chiama la nuova, confronta gli output. Se corrispondono, registra “consistente.” Se differiscono, registra gli input e entrambi gli output. Restituisci il risultato della vecchia implementazione al chiamante indipendentemente, così nessun utente è influenzato dal confronto.
Dopo aver eseguito il parallelo per un periodo sufficiente, rivedi il registro delle discrepanze. Alcune discrepanze saranno bug nella nuova implementazione. Alcune saranno comportamenti non documentati nella vecchia che devi o replicare o cambiare consapevolmente.
Quando usare branch by abstraction vs. strangler fig
Branch by abstraction opera all’interno di un singolo codebase, sostituendo un componente con un altro all’interno della stessa unità distribuibile. Usalo quando stai sostituendo una classe, un modulo, una libreria o un servizio interno.
Lo strangler fig pattern opera a livello di sistema, instradando gradualmente le richieste da un vecchio sistema a uno nuovo usando un livello proxy o gateway. Usalo quando stai migrando da un’applicazione a un’altra, da un monolite a microservizi, o da un’API esterna a una sostituta.
I due pattern condividono lo stesso principio di base: commutazione incrementale con possibilità di rollback, eseguendo vecchio e nuovo in parallelo durante la verifica. Differiscono per portata e per dove vive l’astrazione.
Per la modernizzazione legacy su larga scala che coinvolge sia il refactoring interno che la scomposizione dei servizi, entrambi i pattern sono spesso usati simultaneamente a diversi livelli dell’architettura. Scopri di più nell’approccio completo alla legacy modernization.
Conclusione
Branch by abstraction è l’approccio corretto quando un modulo deve essere sostituito in un sistema di produzione e il downtime o i branch long-lived non sono accettabili. Richiede un investimento iniziale maggiore rispetto a una riscrittura diretta, ma elimina il momento di rischio più alto di qualsiasi migrazione: il deployment big-bang.
I team che applicano questa tecnica in modo consistente sostituiscono componenti che altrimenti richiederebbero riscritture di sei mesi in migrazioni incrementali di sei settimane, con zero downtime e piena capacità di rollback ad ogni passo.
Hai un codebase con questi problemi? Parliamo del tuo sistema