Le modèle Saga : transactions distribuées dans une architecture de microservices

Le modèle Saga : transactions distribuées dans une architecture de microservices

Dans les applications monolithiques traditionnelles, maintenir la cohérence des données entre plusieurs entités est simple. Les moteurs de bases de données relationnelles fournissent des garanties ACID (Atomicité, Cohérence, Isolation, Durabilité) enveloppées dans des transactions SQL locales. Si une commande, une déduction de paiement ou une réserve de stock échoue à mi-parcours, l’appel de ROLLBACK annule instantanément chaque modification de la base de données.

Cependant, lors de la migration vers une architecture de microservices moderne, la gestion des données change fondamentalement. Pour garantir l’autonomie du domaine et une évolutivité indépendante, chaque microservice possède sa base de données privée. Une seule opération commerciale, telle que le traitement d’un règlement de commerce électronique, s’étend désormais sur plusieurs limites de services et moteurs de base de données (par exemple, PostgreSQL pour les commandes, DynamoDB pour les paiements, Redis pour l’inventaire).

Étant donné que les microservices distribués ne peuvent pas s’appuyer sur une seule transaction de base de données, le maintien de la cohérence des données au-delà des frontières du réseau devient l’un des problèmes les plus difficiles de l’ingénierie des systèmes distribués.

Pour résoudre ce problème sans sacrifier la disponibilité ou les performances du système, les architectes logiciels s’appuient sur le Saga Pattern.

Dans cette étude approfondie, nous explorerons les raisons pour lesquelles les transactions distribuées traditionnelles échouent, décomposerons les mécanismes de base du modèle Saga, comparerons Chorégraphie et Orchestration, analyserons les contre-mesures d’isolation, examinerons les implémentations de code de production dans Go et Java, et apprendrons comment gérer en toute sécurité les restaurations en cas d’échec dans le monde réel.


Le problème racine : pourquoi la validation en deux phases (2PC) échoue dans les microservices

Avant d’adopter le modèle Saga, les ingénieurs demandent souvent : Pourquoi ne pouvons-nous pas utiliser la validation en deux phases traditionnelle (2PC / XA) ​​dans nos microservices ?

La mécanique de 2PC

Two-Phase Commit coordonne une transaction distribuée sur plusieurs nœuds de base de données à l’aide d’un gestionnaire de transactions central en deux phases :

  1. Phase de préparation : le gestionnaire de transactions demande à tous les nœuds de base de données participants de préparer et de verrouiller les lignes requises. Les participants votent YES ou NO.
  2. Phase de validation : si tous les participants ont voté YES, le gestionnaire émet une commande COMMIT à tous les nœuds. Si un nœud a voté NO ou a expiré, il émet un ROLLBACK.
Client ----> Transaction Manager
                   |
     +-------------+-------------+
     | (Prepare)   | (Prepare)   | (Prepare)
     v             v             v
 Order DB     Payment DB    Inventory DB

Pourquoi 2PC est un anti-modèle pour les microservices

Bien que 2PC garantisse une forte cohérence, il tombe en panne dans les environnements de microservices cloud natifs en raison de plusieurs défauts architecturaux :

  1. Blocage et conflit de ressources : les lignes de la base de données restent verrouillées tout au long de la négociation réseau en plusieurs étapes. Si la latence du réseau augmente ou si un service ralentit, les verrous restent ouverts, consommant rapidement les pools de connexions et provoquant une panne système en cascade.
  2. Goulot d’étranglement de disponibilité : dans 2PC, la disponibilité du système est limitée par le produit de toutes les disponibilités des participants ($A_{total} = A_1 \times A_2 \times \dots \times A_n$). Si un seul service ou nœud de base de données se déconnecte pendant la phase de préparation, l’ensemble de la transaction globale se bloque indéfiniment.
  3. Perte d’autonomie de service : 2PC oblige les services à exposer les protocoles XA au niveau de la base de données via les API réseau, en couplant directement leurs moteurs de base de données.
  4. Contraintes des courtiers : les courtiers de messages à haut débit comme Apache Kafka ou RabbitMQ ne participent pas de manière native aux transactions XA 2PC traditionnelles entre les bases de données relationnelles.

Selon le théorème CAP, les systèmes distribués doivent choisir entre une forte cohérence (C) et une haute disponibilité (A) sous les partitions réseau (P). Les systèmes de microservices modernes donnent la priorité à la disponibilité et à la tolérance de partition, en échangeant une cohérence instantanée contre une cohérence éventuelle (BASE : disponibilité de base, état souple, cohérence éventuelle).


Qu’est-ce que le modèle Saga ?

Le modèle Saga a été initialement proposé par Hector Garcia-Molina et Kenneth Salem en 1987 comme mécanisme de gestion des transactions de longue durée dans les systèmes de gestion de bases de données. Dans les microservices modernes, une Saga représente une séquence de transactions locales discrètes.

Au lieu d’encapsuler l’intégralité du flux multiservice dans un seul verrou global, une Saga exécute un processus métier sous la forme d’une série de transactions de base de données locales indépendantes ($T_1, T_2, \dots, T_n$).

Chaque transaction locale met à jour les données dans la base de données d’un microservice unique et publie un événement ou un message de domaine. Cet événement déclenche la prochaine transaction locale ($T_{i+1}$) dans le service en aval.

[Order Service]         [Payment Service]       [Inventory Service]
  Local Tx T1 -------------> Local Tx T2 -------------> Local Tx T3
(Create Order)              (Process Payment)            (Reserve Stock)

Exécution à terme et compensation à rebours

Si toutes les transactions locales réussissent, la Saga se termine avec succès (Forward Execution).

Cependant, si une transaction locale échoue à mi-parcours (par exemple, $T_3$ échoue parce qu’un article est en rupture de stock), la Saga ne peut pas simplement appeler une base de données ROLLBACK pour les étapes précédentes ($T_1, T_2$) car ces transactions locales se sont déjà validées dans leurs bases de données respectives.

Pour annuler les transactions locales déjà validées, la Saga doit exécuter des Transactions compensatoires ($C_{n-1}, \dots, C_1$) dans l’ordre inverse.

Forward Flow:    T1 (Create Order) ---> T2 (Charge Card) ---> T3 (Reserve Inventory - FAILS)
                                                                       |
Backward Rollback:  C1 (Cancel Order) <--- C2 (Refund Card) <----------+

Classification des transactions Saga

Pour concevoir un flux de travail Saga robuste, chaque étape de la séquence de transactions doit être classée dans l’un des trois types structurels suivants :

Type de transaction Descriptif Idempotence et exigence de restauration
Opérations rémunérables Étapes exécutées avant le point de non-retour. Ils peuvent être annulés ou inversés si une étape en aval échoue. Doit avoir une opération compensatoire correspondante ($C_i$).
Transaction pivot L’étape définitive de la Saga. Si le Pivot réussit, la Saga est garantie de se terminer. En cas d’échec, la saga revient en arrière. Ni indemnisable, ni récupérable ; il marque la limite entre la restauration et l’achèvement en avant.
Transactions récupérables Étapes exécutées après la transaction Pivot. Ils sont assurés de réussir à terme et ne nécessitent aucune compensation. Doit être strictement idempotent, car ils seront réessayés automatiquement jusqu’à ce qu’ils réussissent.

Répartition de l’exemple de paiement pour le commerce électronique

Considérons un règlement de commande de commerce électronique composé de quatre étapes :

  1. $T_1$ : Créer une commande en attente (Rémunérable) $\to$ Annuler via $C_1$ : Annuler la commande.
  2. $T_2$ : Autoriser le paiement (Rémunérable) $\to$ Annuler via $C_2$ : Rembourser le paiement.
  3. $T_3$ : Réserver l’inventaire (Opération pivot) $\to$ Si l’allocation de stock réussit, la commande est finalisée. En cas d’échec, déclenchez $C_2$ et $C_1$.
  4. $T_4$ : Demande d’expédition d’expédition (Récupérable) $\to$ Exécuté après le pivot ; Réessayé jusqu’à la livraison.

Styles architecturaux de la saga : chorégraphie et orchestration

Il existe deux styles architecturaux principaux pour implémenter le modèle Saga dans les systèmes distribués : Chorégraphie (décentralisée) et Orchestration (centralisée).

Styles d'implémentation de modèles de saga : diagramme d'architecture de chorégraphie et d'orchestration

Style 1 : Chorégraphie (Décentralisation événementielle)

Dans une saga basée sur la chorégraphie, il n’y a pas de contrôleur ou de coordinateur central. Au lieu de cela, les microservices communiquent de manière asynchrone en écoutant les événements de domaine publiés sur un bus d’événements central (tel qu’Apache Kafka, NATS ou RabbitMQ).

Mécanismes de flux de travail

  1. Order Service exécute $T_1$ (crée une commande en attente) et émet un événement OrderCreated à Kafka.
  2. Service de paiement écoute OrderCreated, exécute $T_2$ (facture la carte de crédit) et émet un événement PaymentCompleted (ou PaymentFailed).
  3. Inventory Service écoute PaymentCompleted et exécute $T_3$ (réserve les articles). Si les articles sont en rupture de stock, il émet InventoryReservationFailed.
  4. Service de paiement écoute InventoryReservationFailed et exécute $C_2$ (émet le remboursement).
  5. Order Service écoute PaymentRefunded et exécute $C_1$ (marque la commande comme annulée).

Avantages de la chorégraphie

  • Couplage lâche : les services s’abonnent uniquement aux sujets d’événements ; ils ne connaissent pas les autres implémentations de services.
  • Haut débit et décentralisation : la publication/soumission directe d’événements élimine les goulots d’étranglement de l’orchestrateur central.
  • Simplicité pour les flux de travail courts : facile à configurer pour des flux de travail simples en 2 à 3 étapes.

Inconvénients de la chorégraphie

  • Risque de dépendance cyclique : les services peuvent finir par écouter les événements les uns des autres, créant ainsi des dépendances cycliques complexes.
  • Traçabilité difficile du code : Comprendre le processus métier de bout en bout nécessite de tracer la logique sur plusieurs référentiels de base de code.
  • Event Storming & Complexity : à mesure que le nombre d’étapes augmente (par exemple, plus de 10 services), la gestion des cas extrêmes d’erreur conduit à une explosion de l’état des événements.

Style 2 : Orchestration (gestion centralisée des flux de travail)

Dans une Saga basée sur l’orchestration, un microservice dédié connu sous le nom de Saga Orchestrator contrôle l’intégralité du cycle de vie de la transaction distribuée. L’orchestrateur agit en tant que coordinateur central, émettant des commandes explicites aux microservices participants et écoutant leurs événements de réponse.

Mécanismes de flux de travail

  1. Le client envoie une demande de commande au Saga Orchestrator.
  2. Orchestrator envoie une commande CreateOrder au Order Service. Le service de commande renvoie OrderCreated.
  3. Orchestrator met à jour sa machine d’état et envoie une commande ProcessPayment au Service de paiement. Le service de paiement renvoie PaymentSuccessful.
  4. Orchestrator envoie une commande ReserveInventory à Inventory Service. Le service d’inventaire renvoie InventoryFailed (Out of Stock).
  5. Orchestrator détecte une panne et lance le flux de compensation :
    • Envoie la commande RefundPayment au Service de paiement.
    • Envoie la commande CancelOrder au Order Service.
  6. Orchestrator marque l’exécution de Saga comme FAILED.

Avantages de l’orchestration

  • Logique métier centralisée : l’état du flux de travail et la logique métier sont localisés dans un seul service orchestrateur ou une seule machine à états.
  • Aucune dépendance cyclique : les microservices répondent aux commandes de l’orchestrateur ; ils ne dépendent pas d’autres services en aval et n’en connaissent pas l’existence.
  • Effacer la surveillance et le débogage : l’état de la transaction de bout en bout est explicitement stocké dans le magasin d’état de l’orchestrateur (par exemple, PostgreSQL ou des moteurs de workflow comme Temporal / Camunda).
  • Gestion plus facile des erreurs : l’ajout de nouvelles étapes ou la modification des règles de restauration est entièrement géré à l’intérieur de l’orchestrateur.

Inconvénients de l’orchestration

  • Complexité de l’orchestrateur : risque de placer trop de logique de domaine dans l’orchestrateur, le transformant en un anti-modèle “orchestrateur intelligent, service stupide”.
  • Point de défaillance unique potentiel : l’orchestrateur doit être rendu hautement disponible et avec état.

Matrice comparative : chorégraphie vs orchestration

Fonctionnalité Chorégraphie Orchestration
Structure de contrôle Décentralisé (Pub/Sub d’événement) Centralisé (Coordinateur de Saga / State Machine)
Couplage Extrêmement faible (les services consomment des événements) Medium (Les services acceptent les commandes du coordinateur)
Visibilité des processus Faible (distribué dans les fichiers journaux) Élevé (un magasin d’état unique visualise le flux de travail)
Le mieux adapté pour Flux de travail simples (2 à 4 étapes de service) Workflows d’entreprise complexes (plus de 5 étapes, logique de branchement)
Outils / Frameworks Kafka, RabbitMQ, NATS, AWS EventBridge Temporal.io, AWS Step Functions, Camunda, Axon

Défis et contre-mesures d’isolement (Gestion de “ACID moins I”)

Étant donné que les transactions locales dans une Saga s’engagent immédiatement dans leurs bases de données locales, le modèle Saga ne dispose pas de Isolation (I) par rapport aux garanties ACID traditionnelles.

Si un client lit une ligne de base de données modifiée par $T_1$ alors que la Saga est toujours en cours d’exécution, il lit un état intermédiaire non validé. Si une étape en aval échoue et déclenche une compensation ($C_1$), le client a effectué une Dirty Read.

Anomalies courantes causées par le manque d’isolement

  1. Mises à jour perdues : Saga A met à jour un enregistrement. Avant la fin de la Saga A, la Saga B écrase le même enregistrement. Si Saga A échoue et exécute une compensation, elle écrase la mise à jour de Saga B.
  2. Dirty Reads : Un client lit le stock disponible mis à jour par Saga A ($T_1$). La saga A échoue en aval ($T_3$) et restaure le stock ($C_1$), mais le client a déjà passé une commande basée sur des données obsolètes.
  3. Lectures non répétables : un service lit les données à l’étape $T_1$ et les relit à l’étape $T_3$, mais une autre Saga simultanée a modifié les données entre les deux.

Contre-mesures et stratégies d’atténuation

Pour maintenir l’intégrité des données malgré le manque d’isolation, les architectes logiciels mettent en œuvre des modèles de conception d’isolation spécifiques :

1. Verrouillage sémantique (état en attente / signalé)

Lorsque la transaction locale $T_1$ met à jour un enregistrement de base de données, elle définit un champ d’état sur PENDING ou APPROVAL_REQUIRED (par exemple, ORDER_PENDING_PAYMENT).

Les autres Sagas simultanées lisant cet enregistrement doivent vérifier l’indicateur de verrouillage sémantique et bloquer ou modifier leur comportement jusqu’à ce que l’état passe à COMMITTED ou CANCELLED.

2. Validation de la commande

Concevez la séquence de transactions locales de manière à ce que les opérations à haut risque ou irréversibles se produisent tard dans l’exécution de Saga, minimisant ainsi la fenêtre de vulnérabilité.

3. Validation de relecture (contrôle de concurrence optimiste)

Avant d’exécuter une étape critique ou une compensation, relisez l’enregistrement de la base de données cible et vérifiez les horodatages de la version (version_id) pour vous assurer qu’aucune modification simultanée n’a eu lieu.

4. Vue pessimiste

Réorganisez les étapes d’une saga pour minimiser l’exposition économique (par exemple, placez l’autorisation de paiement aussi près que possible de la transaction pivot).


Flux de séquence de production : transactions compensatoires

Vous trouverez ci-dessous l’organigramme de séquence complet illustrant un échec d’exécution en avant et l’exécution de compensation en arrière qui en résulte :

Diagramme de flux de séquence de modèles Saga montrant l'échec d'exécution avant et les annulations compensatoires

Implémentations pratiques de code

Explorons des exemples d’implémentation prêts pour la production pour la chorégraphie (dans Go) et l’orchestration (dans Java Spring Boot).

Implémentation 1 : Saga basée sur la chorégraphie en Go

Dans cet exemple Go, nous démontrons un Order Service gérant la création d’ordres et écoutant les événements d’échec de paiement via un courtier d’événements pour exécuter une logique de restauration compensatoire.

package saga

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"time"
)

// Event definitions
type OrderCreatedEvent struct {
	OrderID   string  `json:"order_id"`
	CustomerID string `json:"customer_id"`
	Amount    float64 `json:"amount"`
}

type PaymentFailedEvent struct {
	OrderID string `json:"order_id"`
	Reason  string `json:"reason"`
}

// OrderRepository handles local DB operations
type OrderRepository interface {
	CreateOrder(ctx context.Context, orderID string, amount float64) error
	UpdateOrderStatus(ctx context.Context, orderID string, status string) error
}

// EventBus abstraction for message broker (e.g., Kafka / NATS)
type EventBus interface {
	Publish(topic string, payload []byte) error
	Subscribe(topic string, handler func(payload []byte)) error
}

type OrderSagaChoreographer struct {
	repo     OrderRepository
	eventBus EventBus
}

func NewOrderSagaChoreographer(repo OrderRepository, bus EventBus) *OrderSagaChoreographer {
	c := &OrderSagaChoreographer{repo: repo, eventBus: bus}
	c.registerSubscriptions()
	return c
}

// Step 1: Forward Transaction (T1)
func (s *OrderSagaChoreographer) StartOrderSaga(ctx context.Context, orderID, customerID string, amount float64) error {
	// Execute local database transaction
	err := s.repo.CreateOrder(ctx, orderID, amount)
	if err != nil {
		return fmt.Errorf("failed local DB transaction T1: %w", err)
	}

	// Emit domain event for downstream Payment Service
	event := OrderCreatedEvent{OrderID: orderID, CustomerID: customerID, Amount: amount}
	bytes, _ := json.Marshal(event)
	
	log.Printf("[SAGA][T1] Order %s created. Publishing OrderCreatedEvent...", orderID)
	return s.eventBus.Publish("orders.created", bytes)
}

// Register subscription for compensating events
func (s *OrderSagaChoreographer) registerSubscriptions() {
	_ = s.eventBus.Subscribe("payments.failed", func(payload []byte) {
		var event PaymentFailedEvent
		if err := json.Unmarshal(payload, &event); err != nil {
			log.Printf("[ERROR] Corrupt payment event: %v", err)
			return
		}

		// Execute Compensating Transaction (C1)
		s.handlePaymentFailed(context.Background(), event)
	})
}

// Step C1: Compensating Transaction
func (s *OrderSagaChoreographer) handlePaymentFailed(ctx context.Context, event PaymentFailedEvent) {
	log.Printf("[SAGA][C1] Payment failed for Order %s (Reason: %s). Rolling back local order...", event.OrderID, event.Reason)
	
	// Revert order status to CANCELLED in local DB
	err := s.repo.UpdateOrderStatus(ctx, event.OrderID, "CANCELLED_PAYMENT_FAILED")
	if err != nil {
		log.Printf("[CRITICAL] Failed to execute compensating transaction C1 for Order %s: %v", event.OrderID, err)
		// Trigger alert or write to Dead Letter Queue (DLQ)
		return
	}
	
	log.Printf("[SAGA][SUCCESS] Order %s successfully compensated and cancelled.", event.OrderID)
}

Implémentation 2 : Saga basée sur l’orchestration en Java (Spring Boot)

Dans cet exemple Java, nous construisons un Saga Orchestrator à l’aide d’un modèle de machine d’état pour coordonner les commandes avancées et exécuter des restaurations compensatrices en cas d’échec d’une étape en aval.

package com.ghaznix.saga.orchestrator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.UUID;

public enum SagaState {
    STARTED,
    ORDER_CREATED,
    PAYMENT_PROCESSED,
    INVENTORY_RESERVED,
    COMPLETED,
    COMPENSATING_PAYMENT,
    COMPENSATING_ORDER,
    FAILED
}

@Service
public class OrderSagaOrchestrator {

    private static final Logger log = LoggerFactory.getLogger(OrderSagaOrchestrator.class);

    private final OrderServiceClient orderClient;
    private final PaymentServiceClient paymentClient;
    private final InventoryServiceClient inventoryClient;

    public OrderSagaOrchestrator(OrderServiceClient orderClient,
                                PaymentServiceClient paymentClient,
                                InventoryServiceClient inventoryClient) {
        this.orderClient = orderClient;
        this.paymentClient = paymentClient;
        this.inventoryClient = inventoryClient;
    }

    public boolean executeOrderSaga(String customerId, String productId, double amount, int quantity) {
        String sagaId = UUID.randomUUID().toString();
        log.info("[SAGA {}] Starting Order Saga Workflow...", sagaId);
        
        SagaState currentState = SagaState.STARTED;
        String orderId = null;
        String paymentId = null;

        try {
            // Step 1: Forward Local Tx T1 - Create Order
            log.info("[SAGA {}][T1] Sending CreateOrder command...", sagaId);
            orderId = orderClient.createOrder(customerId, productId, amount);
            currentState = SagaState.ORDER_CREATED;

            // Step 2: Forward Local Tx T2 - Process Payment
            log.info("[SAGA {}][T2] Sending ProcessPayment command for Order {}...", sagaId, orderId);
            paymentId = paymentClient.chargePayment(orderId, customerId, amount);
            currentState = SagaState.PAYMENT_PROCESSED;

            // Step 3: Forward Local Tx T3 (Pivot Step) - Reserve Inventory
            log.info("[SAGA {}][T3] Sending ReserveInventory command for Product {}...", sagaId, productId);
            boolean stockReserved = inventoryClient.reserveStock(productId, quantity);

            if (!stockReserved) {
                throw new InventoryAllocationException("Stock allocation failed: Item out of stock.");
            }

            currentState = SagaState.INVENTORY_RESERVED;
            log.info("[SAGA {}][SUCCESS] Saga completed successfully!", sagaId);
            return true;

        } catch (Exception ex) {
            log.error("[SAGA {}][FAILURE] Step failed during state {}. Triggering Compensation...", sagaId, currentState, ex);
            rollbackSaga(sagaId, currentState, orderId, paymentId);
            return false;
        }
    }

    private void rollbackSaga(String sagaId, SagaState failedState, String orderId, String paymentId) {
        log.info("[SAGA {}] Initiating backward compensating transactions from state: {}", sagaId, failedState);

        // Compensate Step 2 if payment was processed
        if (failedState == SagaState.PAYMENT_PROCESSED || failedState == SagaState.INVENTORY_RESERVED) {
            try {
                log.info("[SAGA {}][C2] Executing Payment Refund compensation for Payment {}...", sagaId, paymentId);
                paymentClient.refundPayment(paymentId);
            } catch (Exception e) {
                log.error("[CRITICAL][SAGA {}] Payment refund C2 failed! Manual intervention or DLQ required.", sagaId, e);
            }
        }

        // Compensate Step 1 if order was created
        if (failedState != SagaState.STARTED && orderId != null) {
            try {
                log.info("[SAGA {}][C1] Executing Order Cancel compensation for Order {}...", sagaId, orderId);
                orderClient.cancelOrder(orderId);
            } catch (Exception e) {
                log.error("[CRITICAL][SAGA {}] Order cancellation C1 failed!", sagaId, e);
            }
        }

        log.info("[SAGA {}] Saga compensation workflow finished. Final State: FAILED.", sagaId);
    }
}

Essentiels de la production : associer Saga au modèle de boîte d’envoi transactionnel

Dans la chorégraphie et l’orchestration, l’exécution d’une transaction locale ($T_i$) nécessite la publication d’un événement ou d’une commande de domaine sur le réseau.

Si votre service met à jour sa base de données SQL, puis publie un message sur Kafka, un problème de réseau après la validation de la base de données provoque une perte d’événement silencieuse. À l’inverse, la publication du message avant la validation de la base de données entraîne un traitement d’événements fantômes.

Pour résoudre ce problème, les Sagas doivent être associées au modèle de boîte d’envoi transactionnel :

[Service Database Transaction Boundary]
+-----------------------------------------------------+
| 1. INSERT INTO business_table (orders/payments)    |
| 2. INSERT INTO outbox_table (event_payload)        |
+-----------------------------------------------------+
                          |
             (CDC / Polling Message Relay)
                          |
                          v
               [Message Broker / Kafka]

En conservant la charge utile de l’événement dans une table outbox à l’intérieur de la même transaction de base de données locale, l’atomicité est garantie. Un processus d’arrière-plan asynchrone (comme Debezium ou un relais d’interrogation) lit la table de boîte d’envoi et publie les événements sur Kafka de manière fiable.

De plus, chaque consommateur de services en aval doit mettre en œuvre Idempotency (en utilisant des en-têtes idempotency_key ou de déduplication de message uniques) afin que les livraisons de messages en double lors des tentatives ne déclenchent pas de frais en double ou d’allocations d’inventaire.


Liste de contrôle de décision architecturale

Utilisez cette matrice de décision pratique lors de la conception de transactions distribuées pour les applications de microservices :

                  Do you need cross-service data consistency?
                                     |
                    +----------------+----------------+
                    | No                              | Yes
                    v                                 v
         Standard Single Service            Can you accept Eventual
            Local Database                    Consistency (BASE)?
                                                      |
                                     +----------------+----------------+
                                     | No                              | Yes
                                     v                                 v
                          Use Monolithic Core            Adopt Saga Pattern
                          with Single ACID DB                 |
                                                              |
                                             How complex is the workflow?
                                                              |
                                            +-----------------+-----------------+
                                            | Simple (2-3 steps)                | Complex (4+ steps/branches)
                                            v                                   v
                                   Choreography Saga                   Orchestration Saga
                                   (Event-Driven Bus)                  (Temporal/Custom State Machine)

Conclusion et points clés à retenir

Le Saga Pattern est un modèle architectural essentiel pour gérer les transactions distribuées au-delà des limites des microservices sans verrouiller les ressources ni sacrifier la disponibilité du système.

Liste de contrôle récapitulative :

  1. Abandonnez 2PC/XA dans les microservices cloud natifs : la validation en deux phases entraîne un verrouillage strict, une latence élevée et de graves goulots d’étranglement en matière de disponibilité.
  2. Diviser les transactions en étapes locales : divisez les opérations globales en transactions locales ($T_1 \dots T_n$) associées à des transactions compensatoires d’inversion ($C_1 \dots C_{n-1}$).
  3. Choisissez le bon style architectural : - Utilisez Choreography pour des flux événementiels simples en 2 à 3 étapes avec un couplage lâche. - Utilisez Orchestration pour les flux de travail métier complexes nécessitant une visibilité, des branchements et un suivi des machines d’état centralisés.
  4. Mettez en œuvre des contre-mesures d’isolement : protégez-vous contre les lectures incorrectes et les mises à jour perdues à l’aide de verrous sémantiques (indicateurs PENDING) et du verrouillage optimiste de relecture.
  5. Garantir une messagerie fiable : associez toujours les implémentations de Saga au modèle de boîte d’envoi transactionnel et appliquez les consommateurs idempotents pour gérer les tentatives en toute sécurité.

En mettant en œuvre le modèle Saga de manière réfléchie, vous pouvez créer des microservices hautement disponibles et évolutifs qui restent résilients et cohérents même en cas de panne des réseaux.