Salta al contenuto principale

06 - Deployment e Scaling di Modular Monolith: Scaling Intelligente

Introduzione: Deploy e Scaling Intelligente Uno dei vantaggi principali del modular monolith e la semplicità di deployment : un singolo artefatto, una...

Cosa ne pensi di questo articolo?

06 - Deployment e Scaling di Modular Monolith: Scaling Intelligente
ArchitectureFoto:PNW ProductionPexels

In Breve

Introduzione: Deploy e Scaling Intelligente Uno dei vantaggi principali del modular monolith e la semplicità di deployment : un singolo artefatto, una...

Introduzione: Deploy e Scaling Intelligente

Uno dei vantaggi principali del modular monolith e la semplicità di deployment: un singolo artefatto, una singola pipeline CI/CD, un singolo processo da monitorare. Ma semplicità non significa limitazione. Con le strategie giuste, un modular monolith può essere deployato con zero downtime, scalato in modo intelligente, e gestito con la stessa sofisticazione di un'architettura a microservizi, a una frazione del costo operativo.

In questo articolo esploreremo le strategie di deployment avanzate per il modular monolith: blue-green deployments, feature flags per disaccoppiare deploy e rilascio, auto-scaling basato su metriche, e i criteri per decidere quando estrarre un modulo come microservizio autonomo.

Cosa Imparerai in Questo Articolo

  • Blue-green deployment per aggiornamenti con zero downtime
  • Feature flags: disaccoppiare il deploy dal rilascio delle feature
  • Auto-scaling con Kubernetes HPA e metriche custom
  • Scaling contestuale: ottimizzare le risorse per modulo
  • Database scaling: read replicas, connection pooling, sharding
  • Quando estrarre un modulo come microservizio
  • Configurazione Kubernetes di esempio per il modular monolith

Monolithic Deployment: Build Once, Deploy Everywhere

Il deployment di un modular monolith segue il principio "build once, deploy everywhere": un singolo artefatto (JAR, Docker image) viene costruito una volta e deployato in tutti gli ambienti (staging, production, DR). Questo elimina il rischio di inconsistenze tra ambienti e semplifica drasticamente la pipeline CI/CD.


# Pipeline CI/CD per modular monolith
# Un singolo artefatto per tutti gli ambienti

# .github/workflows/deploy.yml
name: Build and Deploy
on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build application
        run: ./gradlew build

      - name: Build Docker image
        run: |
          docker build -t ecommerce-app:$GITHUB_SHA .

      - name: Push to registry
        run: |
          docker push registry.example.com/ecommerce-app:$GITHUB_SHA

  deploy-staging:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to staging
        run: |
          kubectl set image deployment/ecommerce \
            app=registry.example.com/ecommerce-app:$GITHUB_SHA \
            --namespace staging

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment: production  # richiede approvazione manuale
    steps:
      - name: Deploy to production
        run: |
          kubectl set image deployment/ecommerce \
            app=registry.example.com/ecommerce-app:$GITHUB_SHA \
            --namespace production
    

Blue-Green Deployment: Zero Downtime

Il blue-green deployment mantiene due ambienti identici: blue (attivo, serve il traffico) e green (inattivo, pronto per il prossimo rilascio). Il deploy avviene sull'ambiente inattivo, e lo switch del traffico e istantaneo attraverso il cambio di un load balancer o di un Service Kubernetes.

Vantaggi

  • Zero downtime: lo switch e istantaneo, nessuna interruzione per gli utenti
  • Rollback immediato: se qualcosa va storto, basta tornare all'ambiente precedente
  • Validazione pre-switch: puoi testare il nuovo deploy sull'ambiente green prima dello switch

# Kubernetes: Blue-Green deployment con Service switch
# Blue deployment (attualmente attivo)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ecommerce-blue
  labels:
    app: ecommerce
    version: blue
spec:
  replicas: 4
  selector:
    matchLabels:
      app: ecommerce
      version: blue
  template:
    metadata:
      labels:
        app: ecommerce
        version: blue
    spec:
      containers:
      - name: app
        image: registry.example.com/ecommerce-app:v1.2.0
        ports:
        - containerPort: 8080
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
---
# Service che punta al deployment attivo
apiVersion: v1
kind: Service
metadata:
  name: ecommerce-service
spec:
  selector:
    app: ecommerce
    version: blue    # Cambia in "green" per lo switch
  ports:
  - port: 80
    targetPort: 8080
    

Feature Flags: Disaccoppiare Deploy e Rilascio

I feature flags permettono di deployare codice in produzione senza attivarlo immediatamente. Una feature può essere attivata gradualmente (canary release), per specifici utenti (beta testing), o per specifiche region. Questo disaccoppia completamente il momento del deploy (codice in produzione) dal momento del rilascio (feature visibile agli utenti).


// Feature Flags con Spring Boot e Unleash

@Service
class OrderServiceImpl implements OrderModuleApi {

    private final FeatureFlagService featureFlags;

    @Override
    public OrderDto createOrder(CreateOrderCommand cmd) {
        Order order = Order.create(cmd);

        // Feature flag: nuovo sistema di pricing
        if (featureFlags.isEnabled("new-pricing-engine")) {
            order.applyNewPricing(pricingEngine.calculate(cmd));
        } else {
            order.applyLegacyPricing(cmd.getItems());
        }

        // Feature flag: notifica push (graduale)
        if (featureFlags.isEnabled("push-notifications",
                cmd.getUserId())) {
            notificationModule.sendPush(order);
        }

        orderRepository.save(order);
        return order.toDto();
    }
}

// Configurazione Feature Flags con Unleash
@Configuration
class FeatureFlagConfig {

    @Bean
    public Unleash unleash() {
        return new DefaultUnleash(
            UnleashConfig.builder()
                .appName("ecommerce-app")
                .instanceId("instance-1")
                .unleashAPI("http://unleash:4242/api")
                .build()
        );
    }
}
    

Auto-Scaling con Kubernetes HPA

Il Horizontal Pod Autoscaler (HPA) di Kubernetes scala automaticamente il numero di pod basandosi su metriche di CPU, memoria, o metriche custom. Per un modular monolith, l'HPA scala l'intero applicativo in modo uniforme. E possibile configurare metriche custom basate su indicatori specifici del business come il numero di ordini al minuto.


# HPA per il modular monolith
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: ecommerce-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: ecommerce-app
  minReplicas: 3
  maxReplicas: 12
  metrics:
  # Scala basato su CPU
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  # Scala basato su metriche custom
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: "1000"
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
      - type: Pods
        value: 2
        periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Pods
        value: 1
        periodSeconds: 120
    

Database Scaling

In un modular monolith, il database e spesso il collo di bottiglia prima del compute. Ecco le strategie di scaling per il database:

Read Replicas

Configura read replicas del database e instrada le query di lettura verso le repliche. Le operazioni di scrittura vanno al primary. Questo e particolarmente efficace con il pattern CQRS, dove il read model può essere su una replica dedicata.

Connection Pooling

Usa un connection pooler come PgBouncer per gestire efficientemente le connessioni al database. Con molte istanze del monolith, il numero di connessioni al database può crescere rapidamente. PgBouncer riduce il numero di connessioni effettive al database.

Sharding per Modulo

Se un modulo specifico ha volumi di dati molto superiori agli altri, puoi spostare le sue tabelle su un database dedicato. Questo e un passo intermedio verso l'estrazione come microservizio.

Quando Estrarre un Modulo come Microservizio

Il modular monolith non deve necessariamente rimanere un monolith per sempre. Quando i dati lo giustificano, un modulo può essere estratto come microservizio indipendente. Ecco i criteri decisivi:

Criteri Quantitativi

  • Scaling 10x: il modulo richiede 10 volte più risorse degli altri
  • Frequenza di deploy: il modulo necessità di rilasci 5x più frequenti
  • Latenza SLA: il modulo ha SLA di latenza diversi (es. < 10ms per API critica)
  • Dimensione del team: più di 5 sviluppatori lavorano esclusivamente sul modulo

Criteri Qualitativi

  • Stack tecnologico: il modulo beneficerebbe di un linguaggio o runtime diverso
  • Isolamento dei fallimenti: un bug nel modulo può causare il crash dell'intero sistema
  • Compliance: il modulo gestisce dati con requisiti di sicurezza o compliance specifici
  • Caching: il modulo ha pattern di caching molto diversi dal resto

Regola Pratica per l'Estrazione

Non estrarre un modulo come microservizio finchè non hai almeno 3 criteri quantitativi soddisfatti. L'estrazione prematura introduce complessità distribuita senza benefici proporzionali. Ricorda: il modular monolith e progettato per rendere l'estrazione facile quando serve, non per forzarla.

Configurazione Kubernetes Completa

Ecco una configurazione Kubernetes di riferimento per il deployment di un modular monolith in produzione, con health checks, resource limits, e affinity rules:


# Deployment completo per modular monolith
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ecommerce-app
  labels:
    app: ecommerce
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: ecommerce
  template:
    metadata:
      labels:
        app: ecommerce
    spec:
      containers:
      - name: app
        image: registry.example.com/ecommerce-app:latest
        ports:
        - containerPort: 8080
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "production"
        - name: JAVA_OPTS
          value: "-Xms512m -Xmx2g -XX:+UseG1GC"
        resources:
          requests:
            memory: "1Gi"
            cpu: "500m"
          limits:
            memory: "2Gi"
            cpu: "1000m"
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          initialDelaySeconds: 60
          periodSeconds: 30
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: ecommerce
    

Monitoraggio e Osservabilità

Un vantaggio significativo del modular monolith e la semplicità del monitoraggio. Con un singolo processo, le metriche sono centralizzate, i log sono unificati, e il tracing e locale.

  • Metriche: Spring Boot Actuator + Micrometer per esportare metriche a Prometheus
  • Log: structured logging con correlation ID per tracciare i flussi tra moduli
  • Health checks: endpoint per ogni modulo per verificare lo stato di salute individuale
  • Dashboard: Grafana per visualizzare metriche aggregate e per modulo

Prossimo Articolo

Nel prossimo articolo affronteremo la migrazione step-by-step da un monolith legacy a un modular monolith: il pattern Strangler Fig, l'identificazione dei confini, l'estrazione fisica dei moduli, la migrazione degli eventi, e una timeline realistica con case study e metriche.

Domande frequenti

Quali sono i vantaggi principali del modular monolith rispetto ad altre architetture?

I vantaggi principali del modular monolith includono la semplicità di deployment, la possibilità di scalare in modo intelligente e la gestione con la stessa sofisticazione di un'architettura a microservizi, a una frazione del costo operativo.

Come funziona il blue-green deployment per garantire zero downtime durante il deploy?

Il blue-green deployment mantiene due ambienti identici: blue (attivo, serve il traffico) e green (inattivo, pronto per il prossimo rilascio). Il deploy avviene sull'ambiente inattivo, e lo switch del traffico è istantaneo attraverso il cambio di un load balancer o di un Service Kubernetes.

Quali sono i vantaggi dei feature flags nella gestione del deployment e del rilascio?

I feature flags permettono di deployare codice in produzione senza attivarlo immediatamente, consentendo una graduale attivazione (canary release), per specifici utenti (beta testing) o per specifiche region.

Come si scalano i database in un modular monolith?

Le strategie di scaling per il database includono la configurazione di read replicas del database, l'uso di connection pooler come PgBouncer e lo sharding per modulo.

Fonti e approfondimenti

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti