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 :
- Service Commandes : Créer la commande
- Service Inventaire : Réserver le stock
- Service Paiements : Débiter le client
- 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 :
| Aspect | 2PC | Saga |
|---|---|---|
| Disponibilité | Tous les participants doivent être disponibles simultanément | Chaque étape est indépendante |
| Latence | Ressources bloquées pendant toute la transaction | Chaque étape libère les ressources immédiatement |
| Scalabilité | Les locks distribués limitent le débit (throughput) | Sans locks, chaque service scale indépendamment |
| Couplage | Nécessite un coordinateur qui connaît tous les participants | Les services communiquent par événements |
| Récupération | Si le coordinateur tombe en panne, les participants restent bloqués | Les 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ère | Chorégraphie | Orchestration |
|---|---|---|
| Nombre d’étapes | 2 à 4 étapes simples | 5+ étapes ou flux complexes |
| Visibilité | Acceptable si simple | Nécessaire pour les flux critiques |
| Équipes | Autonomes et indépendantes | Une équipe centrale gère le flux |
| Compensations | Simples et directes | Complexes avec logique conditionnelle |
| Surveillance | Traçabilité distribuée | Tableau 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 :
| Transaction | Compensation |
|---|---|
| Créer la commande | Annuler la commande (changer le statut, sans la supprimer) |
| Réserver le stock | Libérer le stock réservé |
| Débiter le paiement | Émettre un remboursement |
| Planifier la livraison | Annuler 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égorie | Options |
|---|---|
| Orchestrateurs | Temporal, Camunda, AWS Step Functions, Netflix Conductor |
| Messagerie | Apache Kafka, RabbitMQ, Amazon SQS/SNS, Azure Service Bus |
| Frameworks | Axon Framework (Java), MassTransit (.NET), Eventuate Tram |
| Observabilité | Jaeger, Zipkin, OpenTelemetry (tracing distribué) |
| État de la saga | PostgreSQL, 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
| Erreur | Conséquence | Solution |
|---|---|---|
| Compensations non idempotentes | Duplication des remboursements | Utiliser des IDs d’idempotence |
| Ne pas persister l’état de la saga | Sagas perdues après redémarrage | Enregistrer l’état dans une base de données durable |
| Ignorer les échecs de compensation | État incohérent permanent | Réessayer via une dead-letter queue |
| Saga avec trop d’étapes | Complexité ingérable | Diviser en sous-sagas |
| Ne pas surveiller les sagas actives | Sagas bloquées non détectées | Tableau 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.