Strangler Fig Pattern

Strangler Fig Pattern

Strangler Fig Pattern: La Guida Completa per Modernizzare Sistemi Legacy

Nel panorama dello sviluppo software moderno, la migrazione di sistemi legacy rappresenta una delle sfide più complesse che le organizzazioni devono affrontare. Lo Strangler Fig Pattern offre un approccio incrementale alla modernizzazione che permette sia agli investimenti che ai ritorni di avvenire gradualmente e visibilmente, consentendo alle aziende di evolvere il proprio software senza interrompere le operazioni critiche.

Cos’è lo Strangler Fig Pattern

Lo Strangler Fig Pattern è un pattern architetturale che coinvolge il “wrapping” del vecchio codice con l’intento di reindirizzarlo verso nuovo codice. Il nome deriva dalla pianta strangler fig che cresce avvolgendo gli alberi ospiti fino a sostituirli completamente. Martin Fowler coniò questo termine durante una vacanza nelle foreste pluviali del Queensland nel 2001, dopo aver osservato queste piante e notato come il loro processo di crescita graduale rappresentasse perfettamente la strategia di modernizzazione dei sistemi software legacy.

In termini pratici, questo pattern permette di costruire un nuovo sistema attorno al sistema esistente, sostituendo gradualmente le funzionalità una alla volta fino a quando il sistema legacy può essere completamente dismesso.

Quando Nasce e Perché

Il pattern fu introdotto da Martin Fowler nei primi anni 2000 come risposta a un problema ricorrente nell’industria del software: il riconoscimento che la modernizzazione è un processo di scoperta e che permette di rispondere rapidamente alle esigenze del business. A differenza degli approcci “big bang” che tentano di riscrivere completamente un sistema in una sola volta, lo Strangler Fig Pattern riconosce la complessità intrinseca dei sistemi legacy e la necessità di mantenere la continuità operativa.

Come Funziona: I Tre Pilastri

Il processo di transizione da un’applicazione monolitica a microservizi tramite lo Strangler Fig Pattern consiste in tre fasi: Transform, Coexist ed Eliminate.

  1. Transform (Trasformazione): Si identificano e creano componenti modernizzati, portandoli o riscrivendoli in parallelo all’applicazione legacy.
  2. Coexist (Coesistenza): Il sistema legacy e i nuovi microservizi operano fianco a fianco. Una facciata (proxy) intercetta le richieste dirette al sistema backend legacy, instradandole verso l’applicazione legacy o verso i nuovi servizi.
  3. Eliminate (Eliminazione): Man mano che le funzionalità vengono migrate al nuovo sistema, il sistema legacy diventa obsoleto e può essere dismesso.

Perché È Fondamentale nella Migrazione di Sistemi Legacy

Nel settore banking e nelle enterprise applications, dove l’affidabilità e la continuità operativa sono critiche, lo Strangler Fig Pattern si rivela particolarmente efficace. La Commonwealth Bank of Australia ha utilizzato lo Strangler Fig Pattern per modernizzare la propria piattaforma bancaria core, migrando gradualmente moduli specifici senza interrompere la base clienti esistente.

Le ragioni principali per cui questo pattern è preferito nella migrazione di sistemi legacy includono:

Riduzione del Rischio: Ogni cambiamento è limitato in ambito e può essere testato in isolamento prima del deployment in produzione.

Continuità Operativa: Il sistema legacy continua a funzionare durante l’intera transizione, garantendo che le operazioni business critiche non vengano mai interrotte.

Benefici Immediati: Si beneficia dei microservizi quasi immediatamente, non appena si ha il primo servizio, e i vantaggi aumentano costantemente nel tempo con la crescita del numero di servizi.

Flessibilità: Se le esigenze di business cambiano o un determinato approccio non funziona, è più facile cambiare direzione senza perdere una grande quantità di lavoro.

Punti di Forza

  • Migrazione Incrementale: Questo pattern mitiga i rischi associati alla riscrittura completa del sistema e minimizza le interruzioni permettendo un processo di migrazione graduale.
  • Validazione Continua: Ogni componente può essere testato e validato in produzione prima di procedere al successivo, riducendo drasticamente la possibilità di errori catastrofici.
  • Allocazione Risorse Flessibile: I team di sviluppo possono lavorare su diversi componenti in parallelo, ottimizzando l’utilizzo delle risorse disponibili.
  • Learning Progressivo: Si convalida l’approccio all’utilizzo dei microservizi e si impara rapidamente dai propri errori, poiché molto presto nell’effort di modernizzazione si stanno già deployando microservizi in produzione.

Punti Deboli e Considerazioni

Nonostante i numerosi vantaggi, lo Strangler Fig Pattern presenta alcune sfide:

  • Complessità della Facciata: Bisogna assicurarsi che la facciata non diventi un single point of failure o un collo di bottiglia delle prestazioni.
  • Gestione dei Dati: La sincronizzazione dei dati tra sistema legacy e nuovi microservizi può essere complessa, richiedendo strategie sofisticate per mantenere la consistenza.
  • Dipendenze Complesse: I sistemi legacy spesso hanno interdipendenze profonde e complesse che possono rendere difficile l’isolamento di singoli componenti.
  • Overhead Temporaneo: Durante la fase di coesistenza, si devono mantenere sia il vecchio che il nuovo sistema, richiedendo risorse aggiuntive.

Esempi Pratici di Implementazione

Vediamo ora tre esempi pratici di come implementare lo Strangler Fig Pattern utilizzando JavaScript, .NET e Java per creare microservizi e API REST.

Esempio JavaScript (Node.js + Express)

// Facciata Proxy per instradare le richieste
const express = require('express');
const axios = require('axios');
const app = express();

// Configurazione degli endpoint
const LEGACY_SERVICE = 'http://legacy-system:8080';
const NEW_PAYMENT_SERVICE = 'http://new-payment-service:3001';
const NEW_USER_SERVICE = 'http://new-user-service:3002';

// Middleware per il routing intelligente
app.use(express.json());

// Endpoint per i pagamenti - già migrato al nuovo servizio
app.post('/api/payments', async (req, res) => {
  try {
    const response = await axios.post(`${NEW_PAYMENT_SERVICE}/process`, req.body);
    res.json(response.data);
  } catch (error) {
    console.error('Payment service error:', error);
    res.status(500).json({ error: 'Payment processing failed' });
  }
});

// Endpoint per gli utenti - ancora sul sistema legacy
app.get('/api/users/:id', async (req, res) => {
  try {
    // Feature flag per testare gradualmente il nuovo servizio
    const useNewService = req.headers['x-use-new-service'] === 'true';
    
    if (useNewService) {
      const response = await axios.get(`${NEW_USER_SERVICE}/users/${req.params.id}`);
      res.json(response.data);
    } else {
      const response = await axios.get(`${LEGACY_SERVICE}/legacy/users/${req.params.id}`);
      res.json(response.data);
    }
  } catch (error) {
    console.error('User service error:', error);
    res.status(500).json({ error: 'Failed to fetch user' });
  }
});

// Nuovo microservizio per i pagamenti
// new-payment-service.js
const paymentApp = express();
paymentApp.use(express.json());

paymentApp.post('/process', async (req, res) => {
  const { amount, currency, accountId } = req.body;
  
  // Logica moderna per il processing dei pagamenti
  const transaction = {
    id: generateTransactionId(),
    amount,
    currency,
    accountId,
    status: 'processed',
    timestamp: new Date().toISOString()
  };
  
  // Salva nel nuovo database
  await saveToDatabase(transaction);
  
  res.json(transaction);
});

app.listen(3000, () => {
  console.log('Strangler Facade running on port 3000');
});

Esempio .NET (ASP.NET Core)

// Facade API Gateway Program.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using System.Net.Http;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient("LegacyService", client =>
{
    client.BaseAddress = new Uri("http://legacy-system:8080");
});

builder.Services.AddHttpClient("NewOrderService", client =>
{
    client.BaseAddress = new Uri("http://new-order-service:5001");
});

var app = builder.Build();

// Endpoint ordini - migrato al nuovo microservizio
app.MapPost("/api/orders", async (HttpContext context, 
    IHttpClientFactory clientFactory) =>
{
    var client = clientFactory.CreateClient("NewOrderService");
    var requestBody = await new StreamReader(context.Request.Body).ReadToEndAsync();
    
    var response = await client.PostAsync("/orders", 
        new StringContent(requestBody, System.Text.Encoding.UTF8, "application/json"));
    
    var content = await response.Content.ReadAsStringAsync();
    return Results.Ok(content);
});

// Endpoint clienti - ancora sul legacy con feature flag
app.MapGet("/api/customers/{id}", async (string id, HttpContext context,
    IHttpClientFactory clientFactory) =>
{
    // Feature flag per testare gradualmente
    var useNewService = context.Request.Headers["X-Use-New-Service"]
        .FirstOrDefault() == "true";
    
    var clientName = useNewService ? "NewCustomerService" : "LegacyService";
    var client = clientFactory.CreateClient(clientName);
    
    var path = useNewService ? $"/customers/{id}" : $"/legacy/customers/{id}";
    var response = await client.GetAsync(path);
    
    var content = await response.Content.ReadAsStringAsync();
    return Results.Ok(content);
});

app.Run();

// Nuovo Microservizio Ordini - OrderService/Program.cs
var orderBuilder = WebApplication.CreateBuilder(args);
var orderApp = orderBuilder.Build();

orderApp.MapPost("/orders", async (HttpContext context) =>
{
    // Logica moderna per la gestione degli ordini
    var order = await context.Request.ReadFromJsonAsync<Order>();
    
    var processedOrder = new ProcessedOrder
    {
        Id = Guid.NewGuid(),
        CustomerId = order.CustomerId,
        Items = order.Items,
        TotalAmount = order.Items.Sum(i => i.Price * i.Quantity),
        Status = "Confirmed",
        CreatedAt = DateTime.UtcNow
    };
    
    // Salva nel nuovo database
    await SaveToModernDatabase(processedOrder);
    
    return Results.Created($"/orders/{processedOrder.Id}", processedOrder);
});

orderApp.Run();

public record Order(Guid CustomerId, List<OrderItem> Items);
public record OrderItem(string ProductId, int Quantity, decimal Price);
public record ProcessedOrder(Guid Id, Guid CustomerId, List<OrderItem> Items, 
    decimal TotalAmount, string Status, DateTime CreatedAt);

Esempio Java (Spring Boot)

// Facade Gateway Application
@SpringBootApplication
@RestController
public class StranglerFacadeApplication {

    @Autowired
    private RestTemplate restTemplate;
    
    private static final String LEGACY_SERVICE = "http://legacy-system:8080";
    private static final String NEW_ACCOUNT_SERVICE = "http://new-account-service:8081";
    
    public static void main(String[] args) {
        SpringApplication.run(StranglerFacadeApplication.class, args);
    }
    
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    
    // Endpoint conti bancari - migrato al nuovo microservizio
    @PostMapping("/api/accounts/transfer")
    public ResponseEntity<TransferResponse> transfer(
            @RequestBody TransferRequest request) {
        
        try {
            String url = NEW_ACCOUNT_SERVICE + "/transfers/process";
            TransferResponse response = restTemplate.postForObject(
                url, request, TransferResponse.class
            );
            return ResponseEntity.ok(response);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new TransferResponse("failed", e.getMessage()));
        }
    }
    
    // Endpoint transazioni - ancora sul legacy con feature flag
    @GetMapping("/api/transactions/{accountId}")
    public ResponseEntity<List<Transaction>> getTransactions(
            @PathVariable String accountId,
            @RequestHeader(value = "X-Use-New-Service", required = false) 
                Boolean useNewService) {
        
        try {
            String url;
            if (Boolean.TRUE.equals(useNewService)) {
                url = NEW_ACCOUNT_SERVICE + "/transactions/" + accountId;
            } else {
                url = LEGACY_SERVICE + "/legacy/transactions/" + accountId;
            }
            
            Transaction[] transactions = restTemplate.getForObject(
                url, Transaction[].class
            );
            return ResponseEntity.ok(Arrays.asList(transactions));
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
}

// Nuovo Microservizio Account
@SpringBootApplication
@RestController
@RequestMapping("/transfers")
public class NewAccountServiceApplication {
    
    @Autowired
    private TransferRepository transferRepository;
    
    @Autowired
    private EventPublisher eventPublisher;
    
    public static void main(String[] args) {
        SpringApplication.run(NewAccountServiceApplication.class, args);
    }
    
    @PostMapping("/process")
    public ResponseEntity<TransferResponse> processTransfer(
            @RequestBody TransferRequest request) {
        
        // Logica moderna per i trasferimenti bancari
        Transfer transfer = new Transfer();
        transfer.setId(UUID.randomUUID().toString());
        transfer.setFromAccount(request.getFromAccount());
        transfer.setToAccount(request.getToAccount());
        transfer.setAmount(request.getAmount());
        transfer.setStatus("COMPLETED");
        transfer.setTimestamp(Instant.now());
        
        // Salva nel nuovo database
        transferRepository.save(transfer);
        
        // Pubblica evento per altri microservizi
        eventPublisher.publishTransferCompleted(transfer);
        
        return ResponseEntity.ok(new TransferResponse(
            "success", 
            "Transfer completed successfully"
        ));
    }
}

// DTOs
class TransferRequest {
    private String fromAccount;
    private String toAccount;
    private BigDecimal amount;
    // getters e setters
}

class TransferResponse {
    private String status;
    private String message;
    
    public TransferResponse(String status, String message) {
        this.status = status;
        this.message = message;
    }
    // getters e setters
}

Conclusioni e Raccomandazioni per il Team di Sviluppo

Lo Strangler Fig Pattern rappresenta una strategia comprovata e pragmatica per la migrazione di sistemi legacy verso architetture moderne basate su microservizi. La sua adozione nel contesto banking ed enterprise applications ha dimostrato di ridurre significativamente i rischi associati alle grandi migrazioni, mantenendo al contempo la continuità operativa critica per il business.

Investire nella Formazione: La Chiave del Successo

L’implementazione efficace dello Strangler Fig Pattern richiede non solo competenze tecniche, ma anche una comprensione approfondita dei principi architetturali e delle best practices. La formazione del personale IT su questo pattern è fondamentale per ridurre gli errori e accelerare i tempi di sviluppo o migrazione.

Un team ben formato è in grado di:

  • Identificare correttamente i confini dei componenti da migrare
  • Implementare facade e proxy efficienti che non diventino colli di bottiglia
  • Gestire la sincronizzazione dei dati tra sistemi legacy e moderni
  • Applicare feature flags e strategie di deployment progressive
  • Evitare anti-pattern comuni che possono compromettere il successo del progetto

Innovaformazione: Il Partner per la Formazione Specialistica

Per supportare i team di sviluppo nell’adozione dello Strangler Fig Pattern, Innovaformazione si propone come scuola informatica specialistica di riferimento. Su richiesta, Innovaformazione può erogare un corso completo sullo Strangler Fig Pattern, progettato specificamente per sviluppatori software, ingegneri informatici e architetti di sistema.

Caratteristiche del Corso:

  • Modalità: Online in classe virtuale, per garantire massima interattività e flessibilità
  • Calendario: Da concordare in base alle esigenze aziendali
  • Contenuti: Teoria, best practices, esempi pratici e casi di studio reali da settori banking ed enterprise
  • Finanziamenti: Possibilità di finanziare la formazione con Fondimpresa o altri fondi interprofessionali, riducendo significativamente l’investimento economico

Il corso copre tutti gli aspetti fondamentali del pattern, dalla teoria all’implementazione pratica, con esercitazioni su tecnologie moderne come Node.js, .NET Core, Spring Boot, e strumenti per la gestione di microservizi e API REST.

Scopri l’Offerta Formativa Completa

Innovaformazione offre un ampio catalogo di corsi specialistici per lo sviluppo software e l’architettura di sistemi. Visita il catalogo completo all’indirizzo su sito a questo LINK.

Richiedi un Preventivo Personalizzato

Le aziende interessate a investire nella formazione del proprio team IT sullo Strangler Fig Pattern possono richiedere un preventivo personalizzato contattando:

Email: info@innovaformazione.net
Telefono: 3471012275 (Dario Carrassi)

Non aspettare che un progetto di migrazione si trasformi in un fallimento costoso. Investi oggi nella formazione del tuo team e garantisci il successo delle tue iniziative di modernizzazione con lo Strangler Fig Pattern.

(fonte) (fonte) (fonte) (fonte)

Ti potrebbe interessare

Articoli correlati