Strangler Pattern in Java Microservices

Quando si parla di migrazione da un monolite a una microservices architecture, una delle domande più difficili è:

Come facciamo a modernizzare un sistema legacy senza dover riscrivere tutto da zero?

Una delle risposte più interessanti è lo Strangler Pattern.

Il nome deriva dall’idea di una pianta rampicante che cresce attorno a un albero fino a sostituirlo progressivamente.

Ed è esattamente ciò che succede con il software:

il nuovo sistema cresce attorno al vecchio, sostituendolo gradualmente.


Come funziona lo Strangler Pattern?

L’idea è evitare una migrazione del tipo:

MONOLITE
   |
   |  rewrite completo
   ↓
MICROSERVICES

Questo approccio, spesso chiamato Big Bang Rewrite, è estremamente rischioso.

Con lo Strangler Pattern facciamo invece:

                    ┌──────────────┐
Request ───────────>│ API Gateway  │
                    └──────┬───────┘
                           │
                ┌──────────┴──────────┐
                ↓                     ↓
           Legacy Monolith       New Microservice

Inizialmente quasi tutto il traffico continua ad andare al monolite.

Poi iniziamo a estrarre alcune funzionalità.


Step 1: identificare una funzionalità

Supponiamo di avere un monolite Java:

┌───────┐
    MONOLITH            
                                         
    - Users
    - Orders                             
    - Payments                        
    -  Inventory                     
    -  Notifications               
└───────┘

Decidiamo, per esempio, di estrarre Orders.

Creiamo:

Order Service

che diventerà il nuovo microservizio responsabile degli ordini.


Step 2: creare il nuovo microservizio

Possiamo implementarlo con Spring Boot:

@RestController
@RequestMapping("/orders")
public class OrderController {

    @GetMapping("/{id}")
    public Order getOrder(@PathVariable Long id) {
        return orderService.findById(id);
    }
}

A questo punto abbiamo due implementazioni:

              Orders
                |
        ┌───────┴────────┐
        ↓                ↓
    Monolith        Order Service

Il problema diventa:

Chi deve ricevere le richieste?

Ed è qui che entra in gioco il routing.


Step 3: introdurre un API Gateway

Possiamo utilizzare, ad esempio, Spring Cloud Gateway.

Il gateway diventa il punto di ingresso:

                 Client
                   |
                   v
            ┌─────────────┐
            │ API Gateway │
            └──────┬──────┘
                   |
          ┌────────┴────────┐
          ↓                 ↓
      Monolith         Order Service

Una configurazione semplificata potrebbe essere:

@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
    return builder.routes()

        .route("legacy", r -> r.path("/old-api/**")
            .uri("http://monolith-system"))

        .route("new", r -> r.path("/new-api/**")
            .uri("http://new-microservice"))

        .build();
}

Il gateway decide quindi dove mandare la richiesta.


Step 4: spostare progressivamente il traffico

All’inizio potremmo avere:

90% → Monolith
10% → Microservice

Poi:

70% → Monolith
30% → Microservice

E successivamente:

30% → Monolith
70% → Microservice

Fino ad arrivare a:

0% → Monolith
100% → Microservice

A quel punto quella particolare funzionalità è stata completamente migrata.

Possiamo quindi rimuoverla dal monolite.


Lo Strangler Pattern non significa necessariamente “microservices”

Questo è un punto importante.

Lo Strangler Pattern è prima di tutto una strategia di modernizzazione incrementale.

Il risultato potrebbe essere:

Monolith
   +
New Service
   +
New Service

oppure:

Monolith
   +
Microservices
   +
Serverless
   +
External APIs

Il pattern descrive soprattutto come effettuare la transizione, non necessariamente quale debba essere l’architettura finale.


Un esempio più realistico

Immaginiamo un e-commerce legacy:

                 MONOLITH
┌─────────────────────────────────────┐
│                                     │
│ Users       Orders      Payments    │
│                                     │
│ Inventory   Shipping   Notifications│
│                                     │
└─────────────────────────────────────┘

Potremmo procedere così.

Fase 1

Estraggo Notifications:

                 Gateway
                    |
        ┌───────────┴───────────┐
        ↓                       ↓
    Monolith             Notification Service

Fase 2

Estraggo Orders:

                 Gateway
                    |
       ┌────────────┼────────────┐
       ↓            ↓            ↓
   Monolith      Orders      Notifications

Fase 3

Estraggo Payments:

                 Gateway
                    |
    ┌───────────────┼────────────────┐
    ↓               ↓                ↓
 Monolith         Orders          Payments
                    |
              Notifications

Il monolite diventa progressivamente più piccolo.

Alla fine:

                   Gateway
                      |
       ┌──────────────┼──────────────┐
       ↓              ↓              ↓
     Users          Orders        Payments
                      |
                Notifications

E il monolite può finalmente essere eliminato.


Il vero vantaggio: ridurre il rischio

Il principale vantaggio dello Strangler Pattern non è semplicemente “avere microservizi”.

È ridurre il rischio della migrazione.

Una riscrittura completa può durare mesi o anni.

E durante quel periodo abbiamo due problemi:

Vecchio sistema
     +
Nuovo sistema

senza sapere se il nuovo sistema funzionerà davvero.

Con lo Strangler Pattern possiamo invece fare piccoli passi:

Extract
   ↓
Test
   ↓
Deploy
   ↓
Monitor
   ↓
Shift traffic
   ↓
Remove legacy code

Ogni migrazione diventa quindi un cambiamento relativamente piccolo e osservabile.


Un altro vantaggio: rollback

Immaginiamo che il nuovo Order Service abbia un problema.

Con un’architettura basata sul routing possiamo potenzialmente riportare il traffico al monolite:

                    Gateway
                       |
                  ┌────┴────┐
                  ↓         ↓
              Monolith    Service
                 ↑
                 |
              rollback

Questo è molto più semplice rispetto a un Big Bang Rewrite nel quale il vecchio sistema è stato completamente eliminato.


Ma attenzione alla complessità

Lo Strangler Pattern non è gratuito.

Durante la migrazione dobbiamo gestire:

Monolith
   +
Microservices
   +
Gateway
   +
Distributed Data
   +
Observability
   +
Deployment

Per un certo periodo il sistema può diventare più complesso, non meno.

In particolare, uno dei problemi più difficili è spesso il database.

Potremmo infatti avere:

Monolith
    |
    v
Shared Database
    ↑
    |
Order Service

e questo crea un forte accoppiamento.

Una migrazione realmente completa richiede quindi spesso anche una progressiva separazione dei dati:

Monolith → Legacy DB

Order Service → Order DB

Questa parte può essere molto più difficile del semplice spostamento delle API.


Strangler Pattern vs Big Bang

Possiamo riassumere la differenza così:

BIG BANG

Monolith
   |
   | rewrite
   ↓
Microservices

contro:

STRANGLER

Monolith
   |
   +----> New Service
   |
   +----> New Service
   |
   +----> New Service
   |
   ↓
Monolith sempre più piccolo
   |
   ↓
Monolith eliminato

Il secondo approccio non elimina il rischio.

Lo distribuisce nel tempo e permette di verificare continuamente che ogni passo funzioni.


La lezione architetturale

Lo Strangler Pattern ci ricorda una cosa importante:

modernizzare un sistema legacy non significa necessariamente riscriverlo.

Spesso è più efficace costruire il nuovo sistema accanto al vecchio e spostare progressivamente le responsabilità.

Il pattern diventa quindi particolarmente interessante quando abbiamo:

  • sistemi legacy molto grandi;
  • business che non può permettersi lunghi downtime;
  • applicazioni difficili da riscrivere;
  • necessità di modernizzazione graduale;
  • team che devono continuare a sviluppare nuove funzionalità durante la migrazione.

In definitiva, lo Strangler Pattern non è soltanto un pattern tecnico.

È una strategia di gestione del rischio architetturale:

Legacy
   ↓
Extract
   ↓
Route
   ↓
Observe
   ↓
Migrate
   ↓
Remove

Un passo alla volta.

Ed è proprio questo che rende possibile trasformare un monolite in una nuova architettura senza dover fare un salto nel vuoto.

#softwarearchitecture #microservices #java #springboot #designpatterns #legacy #cloud #softwareengineering

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *