Saga Pattern

Comment gérer les transactions distribuées entre microservices à l'aide de la chorégraphie ou de l'orchestration avec compensations.

Quel problème cela résout-il

Dans un monolithe, une transaction de base de données peut porter sur plusieurs tables avec un simple BEGIN → COMMIT → ROLLBACK. Mais dans une architecture de microservices, chaque service possède sa propre base de données. Il n’existe pas de transaction globale capable de faire un rollback sur 5 bases de données différentes si quelque chose échoue à l’étape 3.

Le Saga Pattern résout ce problème en divisant une transaction distribuée en une séquence de transactions locales, où chaque étape dispose d’une action compensatoire qui annule son effet si une étape ultérieure échoue.

Exemple du problème

Imaginez un flux d’achat dans un e-commerce :

  1. Service Commandes : Créer la commande
  2. Service Inventaire : Réserver le stock
  3. Service Paiements : Débiter le client
  4. Service Livraisons : Planifier la livraison

Si le paiement échoue à l’étape 3, il faut annuler la réservation de stock (étape 2) et annuler la commande (étape 1). Sans pattern défini, cela se transforme en un chaos d’appels manuels et d’états incohérents.

Pourquoi ne pas utiliser les transactions distribuées (2PC)

Le protocole Two-Phase Commit (2PC) semble être la solution évidente, mais il pose des problèmes sérieux dans les microservices :

Aspect2PCSaga
DisponibilitéTous les participants doivent être disponibles simultanémentChaque étape est indépendante
LatenceRessources bloquées pendant toute la transactionChaque étape libère les ressources immédiatement
ScalabilitéLes locks distribués limitent le débit (throughput)Sans locks, chaque service scale indépendamment
CouplageNécessite un coordinateur qui connaît tous les participantsLes services communiquent par événements
RécupérationSi le coordinateur tombe en panne, les participants restent bloquésLes compensations s’exécutent de manière asynchrone

Comment ça fonctionne

Une saga est une séquence de transactions locales où :

  • Chaque transaction met à jour des données au sein d’un seul service
  • Chaque transaction publie un événement ou un message qui déclenche la suivante
  • Si une transaction échoue, des compensations sont exécutées dans l’ordre inverse
Flujo exitoso:
  T1 → T2 → T3 → T4 ✅

Flujo con fallo en T3:
  T1 → T2 → T3 ❌ → C2 → C1
  (C = compensación)

Il existe deux stratégies principales pour coordonner une saga :

Chorégraphie (Choreography)

Chaque service écoute les événements et décide quoi faire. Il n’y a pas de coordinateur central.

┌──────────┐    PedidoCreado     ┌──────────────┐
│ Pedidos  │ ──────────────────► │  Inventario  │
└──────────┘                     └──────┬───────┘

                                  StockReservado


                                 ┌──────────────┐
                                 │    Pagos     │
                                 └──────┬───────┘

                                   PagoAprobado


                                 ┌──────────────┐
                                 │   Envíos     │
                                 └──────────────┘

Avantages : pas de point unique de défaillance, services découplés, facilité d’ajout de participants. Inconvénients : difficile de comprendre le flux complet, risque de cycles, débogage difficile.

Orchestration

Un composant central coordonne toute la saga. Il sait quelles étapes exécuter et quelles compensations appliquer.

                    ┌──────────────────┐
                    │   Orquestador    │
                    │   de Saga        │
                    └──┬───┬───┬───┬──┘
                       │   │   │   │
            Crear      │   │   │   │  Programar
            pedido     │   │   │   │  envío
                       ▼   │   │   ▼
                 ┌────────┐│   │┌────────┐
                 │Pedidos ││   ││Envíos  │
                 └────────┘│   │└────────┘
                    Reservar   │  Cobrar
                    stock  │   │  pago
                           ▼   ▼
                    ┌────────┐┌────────┐
                    │Invent. ││ Pagos  │
                    └────────┘└────────┘

Avantages : flux centralisé, contrôle explicite des compensations, débogage facile. Inconvénients : point unique de défaillance potentiel, risque de God Service, couplage.

Quand choisir chaque stratégie

CritèreChorégraphieOrchestration
Nombre d’étapes2 à 4 étapes simples5+ étapes ou flux complexes
VisibilitéAcceptable si simpleNécessaire pour les flux critiques
ÉquipesAutonomes et indépendantesUne équipe centrale gère le flux
CompensationsSimples et directesComplexes avec logique conditionnelle
SurveillanceTraçabilité distribuéeTableau de bord centralisé

Transactions compensatoires

Les compensations ne sont pas un simple « undo ». Ce sont des actions métier qui inversent sémantiquement l’effet d’une transaction antérieure :

TransactionCompensation
Créer la commandeAnnuler la commande (changer le statut, sans la supprimer)
Réserver le stockLibérer le stock réservé
Débiter le paiementÉmettre un remboursement
Planifier la livraisonAnnuler la planification de la livraison

Points clés sur les compensations :

  • Ce ne sont pas des rollbacks : elles ne suppriment pas les données, elles créent de nouveaux enregistrements qui annulent l’effet
  • Elles doivent être idempotentes : exécuter plusieurs fois la même compensation produit le même résultat
  • Elles peuvent échouer : des nouvelles tentatives sont nécessaires pour les compensations en échec
  • Elles maintiennent une piste d’audit : tout est enregistré (action d’origine et compensation)

Exemple pratique : saga d’achat avec orchestration

Chemin nominal (happy path)

Orquestador          Pedidos    Inventario    Pagos      Envíos
    │                   │           │           │          │
    │── CrearPedido ───►│           │           │          │
    │◄── PedidoCreado ──│           │           │          │
    │                   │           │           │          │
    │── ReservarStock ──────────────►│           │          │
    │◄── StockReservado ────────────│           │          │
    │                   │           │           │          │
    │── CobrarPago ─────────────────────────────►│          │
    │◄── PagoAprobado ──────────────────────────│          │
    │                   │           │           │          │
    │── ProgramarEnvío ─────────────────────────────────────►│
───────────────────────────│
    │                   │           │           │          │
    ✅ Saga completada

Échec du paiement (avec compensations)

Orquestador          Pedidos    Inventario    Pagos
    │                   │           │           │
    │── CrearPedido ───►│           │           │
    │◄── PedidoCreado ──│           │           │
    │                   │           │           │
    │── ReservarStock ──────────────►│           │
    │◄── StockReservado ────────────│           │
    │                   │           │           │
    │── CobrarPago ─────────────────────────────►│
    │◄── PagoRechazado ────────────────────────│  ❌
    │                   │           │           │
    │  ┌── COMPENSACIONES ──────────────────┐  │
    │  │── LiberarStock ────────────►│       │  │
    │  │◄── StockLiberado ──────────│       │  │
    │  │── CancelarPedido ──►│       │       │  │
    │  │◄── PedidoCancelado ─│       │       │  │
    │  └────────────────────────────────────┘  │
             │           │           │
    ❌ Saga compensada

État persistant de l’orchestrateur

{
  "sagaId": "saga-12345",
  "tipo": "CompraCompleta",
  "estado": "COMPENSANDO",
  "pasoActual": "LiberarStock",
  "datos": {
    "pedidoId": "ped-001",
    "clienteId": "cli-789",
    "monto": 150.00
  },
  "historial": [
    { "paso": "CrearPedido", "estado": "COMPLETADO" },
    { "paso": "ReservarStock", "estado": "COMPLETADO" },
cientes" },
    { "paso": "LiberarStock", "estado": "COMPENSANDO" }
  ]
}

Avantages

  • Cohérence à terme : garantit que le système atteindra un état cohérent
  • Autonomie des services : chaque service conserve sa propre base de données et ses transactions locales
  • Résilience : si une étape échoue, les compensations restaurent la cohérence
  • Scalabilité : ne nécessite ni locks distribués ni coordination synchrone
  • Traçabilité naturelle : chaque étape et compensation est enregistrée sous forme d’événement
  • Flexibilité : il est possible d’ajouter ou de modifier des étapes sans affecter tout le flux

Trade-offs / Inconvénients

  • Complexité : concevoir des compensations correctes pour chaque étape n’est pas trivial
  • Cohérence à terme : le système peut être temporairement incohérent
  • Difficile à déboguer : suivre une saga nécessite de bons outils d’observabilité
  • Compensations imparfaites : il n’est pas toujours possible d’annuler complètement une action
  • Ordre d’exécution : en chorégraphie, garantir le bon ordre est complexe
  • Tests : tester tous les chemins possibles demande un effort important
  • Fenêtre d’incohérence : pendant l’exécution, les données peuvent se trouver dans un état partiel

Quand l’utiliser

  • Transactions qui traversent plusieurs microservices avec des bases de données indépendantes
  • Flux métier de longue durée (minutes, heures ou jours)
  • Systèmes où la cohérence à terme est acceptable
  • Lorsque vous avez besoin d’une traçabilité complète de chaque étape du processus
  • Flux comportant des étapes qui peuvent échouer et nécessitent une compensation définie
  • Systèmes event-driven où les services communiquent déjà par événements

Quand l’éviter

  • Opérations nécessitant une cohérence forte et immédiate (ACID)
  • Flux simples n’impliquant que 2 services (cela peut être excessif)
  • Lorsque les compensations sont impossibles ou n’ont pas de sens
  • Systèmes où la fenêtre d’incohérence n’est pas acceptable
  • Équipes sans expérience des systèmes distribués
  • Lorsqu’un monolithe avec des transactions locales résout le problème

Outils et implémentations courantes

CatégorieOptions
OrchestrateursTemporal, Camunda, AWS Step Functions, Netflix Conductor
MessagerieApache Kafka, RabbitMQ, Amazon SQS/SNS, Azure Service Bus
FrameworksAxon Framework (Java), MassTransit (.NET), Eventuate Tram
ObservabilitéJaeger, Zipkin, OpenTelemetry (tracing distribué)
État de la sagaPostgreSQL, Redis, DynamoDB

Relation avec d’autres patterns

La saga s’articule avec plusieurs patterns de résilience :

┌─────────────────────────────────────────────────┐
│              Ecosistema de la Saga               │
│                                                  │
│  ┌──────────┐    publica     ┌──────────────┐   │
│  │  Saga    │ ──────────────►│   Outbox     │   │
│  │ (pasos)  │    eventos     │  (confiable) │   │
│  └────┬─────┘                └──────────────┘   │
│       │                                          │
│       │ cada paso es                             │
│       │ idempotente                    │
│       ▼                                          │
│  ┌──────────────┐     protege    ┌───────────┐  │
│  │ Idempotency  │ ◄────────────►│  Circuit   │  │
│  │   Pattern    │   llamadas    │  Breaker   │  │
│  └──────────────┘               └───────────┘  │
│                                                  │
│  ┌──────────┐     reintentos    ┌───────────┐  │
│  │  Retry   │ ◄────────────────►│  Timeout  │  │
│  │ Pattern  │   con límite     │  Pattern  │  │
 └───────────┘  │
└─────────────────────────────────────────────────┘
  • Outbox Pattern : garantit que les événements de chaque étape sont publiés de manière fiable
  • Idempotency Pattern : chaque étape et compensation doit être idempotente pour permettre des nouvelles tentatives sûres
  • Circuit Breaker : protège l’orchestrateur lorsqu’un service participant est en panne
  • Retry Pattern : retente les étapes en échec avec un backoff avant d’activer les compensations
  • Timeout Pattern : définit combien de temps attendre avant de considérer une étape comme échouée
  • Event-Driven Architecture : la saga repose sur un système d’événements
  • DDD : les bounded contexts définissent les limites de chaque participant

Erreurs courantes

ErreurConséquenceSolution
Compensations non idempotentesDuplication des remboursementsUtiliser des IDs d’idempotence
Ne pas persister l’état de la sagaSagas perdues après redémarrageEnregistrer l’état dans une base de données durable
Ignorer les échecs de compensationÉtat incohérent permanentRéessayer via une dead-letter queue
Saga avec trop d’étapesComplexité ingérableDiviser en sous-sagas
Ne pas surveiller les sagas activesSagas bloquées non détectéesTableau de bord avec alertes

Prochaines étapes

Une fois que vous maîtrisez le Saga Pattern, explorez le Idempotency Pattern pour vous assurer que les étapes de la saga sont sûres face aux nouvelles tentatives.