El patrón Saga: transacciones distribuidas en arquitectura de microservicios
En las aplicaciones monolíticas tradicionales, mantener la coherencia de los datos entre múltiples entidades es sencillo. Los motores de bases de datos relacionales proporcionan garantías ACID (Atomicidad, Consistencia, Aislamiento, Durabilidad) incluidas en transacciones SQL locales. Si la realización de un pedido, una deducción de pago o una reserva de inventario fallan a la mitad, llamar a ROLLBACK revierte instantáneamente cada modificación de la base de datos.
Sin embargo, al migrar a una Arquitectura de Microservicios moderna, la gestión de datos cambia fundamentalmente. Para garantizar la autonomía del dominio y la escalabilidad independiente, cada microservicio posee su base de datos privada. Una sola operación comercial, como procesar un pago de comercio electrónico, ahora abarca múltiples límites de servicios y motores de bases de datos (por ejemplo, PostgreSQL para pedidos, DynamoDB para pagos, Redis para inventario).
Debido a que los microservicios distribuidos no pueden depender de una única transacción de base de datos, mantener la coherencia de los datos a través de los límites de la red se convierte en uno de los problemas más desafiantes en la ingeniería de sistemas distribuidos.
Para resolver esto sin sacrificar la disponibilidad o el rendimiento del sistema, los arquitectos de software confían en Saga Pattern.
En esta inmersión profunda, exploraremos por qué fallan las transacciones distribuidas tradicionales, analizaremos la mecánica central del patrón Saga, compararemos Coreografía vs. Orquestación, analizaremos contramedidas de aislamiento, examinaremos las implementaciones de código de producción en Go y Java y aprenderemos cómo manejar de forma segura las reversiones de fallas del mundo real.
El problema raíz: por qué falla el compromiso de dos fases (2PC) en microservicios
Antes de adoptar el patrón Saga, los ingenieros suelen preguntar: ¿Por qué no podemos utilizar el compromiso de dos fases tradicional (2PC/XA) en todos nuestros microservicios?
La mecánica de 2PC
El compromiso en dos fases coordina una transacción distribuida en múltiples nodos de bases de datos utilizando un administrador de transacciones central en dos fases:
- Fase de preparación: el Administrador de transacciones solicita a todos los nodos de la base de datos participantes que preparen y bloqueen las filas requeridas. Los participantes votan
YESoNO. - Fase de confirmación: si todos los participantes votaron
YES, el Administrador emite un comandoCOMMITa todos los nodos. Si algún nodo votóNOo se agotó el tiempo de espera, emite unROLLBACK.
Client ----> Transaction Manager
|
+-------------+-------------+
| (Prepare) | (Prepare) | (Prepare)
v v v
Order DB Payment DB Inventory DB
Por qué 2PC es un antipatrón para microservicios
Si bien 2PC garantiza una gran coherencia, falla en entornos de microservicios nativos de la nube debido a varios defectos arquitectónicos:
- Bloqueo y contención de recursos: las filas de la base de datos permanecen bloqueadas durante el protocolo de enlace de red de varias etapas. Si la latencia de la red aumenta o un servicio se ralentiza, los bloqueos se mantienen abiertos, consumiendo rápidamente los grupos de conexiones y provocando fallos en cascada del sistema.
- Cuello de botella de disponibilidad: En 2PC, la disponibilidad del sistema está limitada por el producto de todas las disponibilidades de los participantes ($A_{total} = A_1 \times A_2 \times \dots \times A_n$). Si un solo servicio o nodo de base de datos se desconecta durante la fase de preparación, toda la transacción global se bloquea indefinidamente.
- Pérdida de autonomía del servicio: 2PC obliga a los servicios a exponer protocolos XA a nivel de base de datos en las API de la red, acoplando sus motores de bases de datos directamente.
- Restricciones del intermediario: los intermediarios de mensajes de alto rendimiento como Apache Kafka o RabbitMQ no participan de forma nativa en transacciones XA 2PC tradicionales entre bases de datos relacionales.
Según el Teorema CAP, los sistemas distribuidos deben elegir entre Fuerte consistencia (C) y Alta disponibilidad (A) en Particiones de red (P). Los sistemas de microservicios modernos priorizan la Disponibilidad y Tolerancia de Partición, intercambiando consistencia instantánea por Coherencia Eventual (BASE: Básicamente Disponible, Estado Suave, Coherencia Eventual).
¿Qué es el Patrón Saga?
El patrón Saga fue propuesto originalmente por Héctor García-Molina y Kenneth Salem en 1987 como un mecanismo para manejar transacciones de larga duración en sistemas de gestión de bases de datos. En los microservicios modernos, una Saga representa una secuencia de transacciones locales discretas.
En lugar de envolver todo el flujo multiservicio dentro de un único bloqueo global, una Saga ejecuta un proceso de negocio como una serie de transacciones de bases de datos locales independientes ($T_1, T_2, \dots, T_n$).
Cada transacción local actualiza los datos en la base de datos de un único microservicio y publica un evento o mensaje de dominio. Este evento desencadena la siguiente transacción local ($T_{i+1}$) en el servicio descendente.
[Order Service] [Payment Service] [Inventory Service]
Local Tx T1 -------------> Local Tx T2 -------------> Local Tx T3
(Create Order) (Process Payment) (Reserve Stock)
Ejecución hacia adelante versus compensación hacia atrás
Si todas las transacciones locales tienen éxito, la Saga se completa con éxito (ejecución directa).
Sin embargo, si una transacción local falla a la mitad (por ejemplo, $T_3$ falla porque un artículo está agotado), Saga no puede simplemente llamar a una base de datos ROLLBACK para los pasos anteriores ($T_1, T_2$) porque esas transacciones locales ya se han comprometido con sus respectivas bases de datos.
Para deshacer transacciones locales ya comprometidas, Saga debe ejecutar Transacciones de compensación ($C_{n-1}, \dots, C_1$) en orden inverso.
Forward Flow: T1 (Create Order) ---> T2 (Charge Card) ---> T3 (Reserve Inventory - FAILS)
|
Backward Rollback: C1 (Cancel Order) <--- C2 (Refund Card) <----------+
Clasificación de transacciones de Saga
Para diseñar un flujo de trabajo Saga sólido, cada paso de la secuencia de transacciones debe clasificarse en uno de tres tipos estructurales:
| Tipo de transacción | Descripción | Requisito de idempotencia y reversión |
|---|---|---|
| Transacciones compensables | Pasos ejecutados antes del punto de no retorno. Se pueden deshacer o revertir si falla un paso posterior. | Debe tener una Transacción Compensadora correspondiente ($C_i$). |
| Transacción dinámica | El paso definitivo en la Saga. Si el Pivote tiene éxito, la Saga tiene garantizado su fin. Si falla, la saga retrocede. | Ni compensable ni procesable; marca el límite entre la reversión y la finalización hacia adelante. |
| Transacciones recuperables | Pasos ejecutados después de la transacción Pivot. Se garantiza que eventualmente tendrán éxito y no requieren compensación. | Debe ser estrictamente idempotente, ya que se volverán a intentar automáticamente hasta que tengan éxito. |
Desglose del ejemplo de pago de comercio electrónico
Considere el pago de un pedido de comercio electrónico que consta de cuatro pasos:
- $T_1$: Crear pedido pendiente (Compensable) $\to$ Deshacer mediante $C_1$: Cancelar pedido.
- $T_2$: Autorizar pago (Compensable) $\to$ Deshacer mediante $C_2$: Reembolsar pago.
- $T_3$: Inventario de reserva (Transacción dinámica) $\to$ Si la asignación de existencias se realiza correctamente, el pedido se finaliza. Si falla, activa $C_2$ y $C_1$.
- $T_4$: Solicitud de envío de envío (Retriable) $\to$ Ejecutada después del pivote; Se volvió a intentar hasta la entrega.
Estilos arquitectónicos de saga: coreografía versus orquestación
Hay dos estilos arquitectónicos principales para implementar el patrón Saga en sistemas distribuidos: Coreografía (descentralizada) y Orquestación (centralizada).
Estilo 1: Coreografía (descentralización impulsada por eventos)
En una Saga basada en coreografía, no hay un controlador o coordinador central. En cambio, los microservicios se comunican de forma asincrónica escuchando los eventos de dominio publicados en un bus de eventos central (como Apache Kafka, NATS o RabbitMQ).
Mecánica del flujo de trabajo
- Servicio de pedidos ejecuta $T_1$ (crea un pedido pendiente) y emite un evento
OrderCreateda Kafka. - Servicio de pago escucha
OrderCreated, ejecuta $T_2$ (carga a la tarjeta de crédito) y emite un eventoPaymentCompleted(oPaymentFailed). - Servicio de inventario escucha
PaymentCompleted, ejecuta $T_3$ (reserva artículos). Si los artículos están agotados, emiteInventoryReservationFailed. - Servicio de pago escucha
InventoryReservationFailedy ejecuta $C_2$ (emite reembolso). - Servicio de pedidos escucha
PaymentRefundedy ejecuta $C_1$ (marca el pedido como cancelado).
Ventajas de la coreografía
- Acoplamiento suelto: los servicios solo se suscriben a temas de eventos; no conocen otras implementaciones de servicios.
- Alto rendimiento y descentralización: la publicación/suscripción directa de eventos elimina los cuellos de botella del orquestador central.
- Simplicidad para flujos de trabajo cortos: fácil de configurar para flujos de trabajo simples de 2 a 3 pasos.
Desventajas de la coreografía
- Riesgo de dependencia cíclica: los servicios pueden terminar escuchando los eventos de los demás, creando dependencias cíclicas complejas.
- Trazabilidad de código difícil: comprender el proceso empresarial de un extremo a otro requiere una lógica de seguimiento en múltiples repositorios de base de código.
- Tormenta de eventos y complejidad: a medida que aumenta el número de pasos (por ejemplo, más de 10 servicios), la gestión de casos extremos de error conduce a una explosión del estado de los eventos.
Estilo 2: Orquestación (Gestión centralizada del flujo de trabajo)
En una Saga basada en orquestación, un microservicio dedicado conocido como Saga Orchestrator controla todo el ciclo de vida de la transacción distribuida. El orquestador actúa como coordinador central, emite comandos explícitos a los microservicios participantes y escucha sus eventos de respuesta.
Mecánica del flujo de trabajo
- El cliente envía una solicitud de pedido al Saga Orchestrator.
- Orchestrator envía un comando
CreateOrderal Servicio de pedidos. El servicio de pedidos devuelveOrderCreated. - Orchestrator actualiza su máquina de estado y envía un comando
ProcessPaymental Servicio de pago. El servicio de pago devuelvePaymentSuccessful. - Orchestrator envía un comando
ReserveInventoryal Servicio de inventario. El servicio de inventario devuelveInventoryFailed (Out of Stock). - El orquestador detecta una falla e inicia el flujo de compensación:
- Envía el comando
RefundPaymental Servicio de pago. - Envía el comando
CancelOrderal Servicio de pedidos.
- Envía el comando
- El orquestador marca la ejecución de Saga como
FAILED.
Ventajas de la orquestación
- Lógica empresarial centralizada: el estado del flujo de trabajo y la lógica empresarial se localizan en un único servicio de orquestador o máquina de estado.
- Sin dependencias cíclicas: los microservicios responden a comandos del orquestador; no dependen ni conocen otros servicios posteriores.
- Borrar supervisión y depuración: el estado de la transacción de un extremo a otro se almacena explícitamente en el almacén de estado del orquestador (por ejemplo, PostgreSQL o motores de flujo de trabajo como Temporal/Camunda).
- Manejo de errores más sencillo: agregar nuevos pasos o cambiar las reglas de reversión se administra completamente dentro del orquestador.
Desventajas de la orquestación
- Complejidad del orquestador: riesgo de colocar demasiada lógica de dominio en el orquestador, convirtiéndolo en un antipatrón de “orquestador inteligente, servicio tonto”.
- Posible punto único de error: el orquestador debe tener alta disponibilidad y estado.
Matriz comparativa: coreografía versus orquestación
| Característica | Coreografía | Orquestación |
|---|---|---|
| Estructura de control | Descentralizado (Evento Pub/Sub) | Centralizado (Coordinador Saga / Máquina de Estado) |
| Acoplamiento | Extremadamente bajo (eventos de consumo de servicios) | Medio (Los servicios aceptan comandos del Coordinador) |
| Visibilidad del proceso | Bajo (distribuido entre archivos de registro) | Alto (la tienda de estado único visualiza el flujo de trabajo) |
| Más adecuado para | Flujos de trabajo simples (de 2 a 4 pasos de servicio) | Flujos de trabajo empresariales complejos (más de 5 pasos, lógica de ramificación) |
| Herramientas/Marcos | Kafka, RabbitMQ, NATS, AWS EventBridge | Temporal.io, funciones escalonadas de AWS, Camunda, Axon |
Desafíos y contramedidas de aislamiento (manejo de “ACID menos I”)
Debido a que las transacciones locales en una Saga se comprometen inmediatamente con sus bases de datos locales, el patrón Saga carece de Aislamiento (I) de las garantías ACID tradicionales.
Si un cliente lee una fila de la base de datos modificada por $T_1$ mientras Saga aún se está ejecutando, está leyendo estado intermedio no confirmado. Si un paso posterior falla y genera una compensación ($C_1$), el cliente ha realizado una Lectura sucia.
Anomalías comunes causadas por la falta de aislamiento
- Actualizaciones perdidas: Saga A actualiza un registro. Antes de que se complete la Saga A, la Saga B sobrescribe el mismo registro. Si Saga A falla y ejecuta la compensación, sobrescribe la actualización de Saga B.
- Lecturas sucias: un cliente lee el stock disponible actualizado por Saga A ($T_1$). La saga A falla en sentido descendente ($T_3$) y restablece el stock ($C_1$), pero el cliente ya realizó un pedido basado en datos obsoletos.
- Lecturas no repetibles: un servicio lee datos en el paso $T_1$ y los lee nuevamente en el paso $T_3$, pero otra Saga concurrente modificó los datos en el medio.
Contramedidas y estrategias de mitigación
Para mantener la integridad de los datos a pesar de la falta de aislamiento, los arquitectos de software implementan patrones de diseño de aislamiento específicos:
1. Bloqueo semántico (estado pendiente/marcado)
Cuando la transacción local $T_1$ actualiza un registro de la base de datos, establece un campo de estado en PENDING o APPROVAL_REQUIRED (por ejemplo, ORDER_PENDING_PAYMENT).
Otras Sagas concurrentes que lean este registro deben verificar el indicador de bloqueo semántico y bloquear o alterar su comportamiento hasta que el estado cambie a COMMITTED o CANCELLED.
2. Orden de confirmación
Diseñe la secuencia de transacciones locales para que las operaciones irreversibles o de alto riesgo ocurran al final de la ejecución de Saga, minimizando la ventana de vulnerabilidad.
3. Validación de relectura (control de concurrencia optimista)
Antes de ejecutar un paso crítico o una compensación, vuelva a leer el registro de la base de datos de destino y verifique las marcas de tiempo de la versión (version_id) para asegurarse de que no se haya producido ninguna modificación simultánea.
4. Visión pesimista
Reordenar los pasos de una Saga para minimizar la exposición económica (por ejemplo, colocar la autorización de pago lo más cerca posible de la transacción dinámica).
Flujo de secuencia de producción: transacciones de compensación
A continuación se muestra el diagrama de flujo de secuencia completo que ilustra un error de ejecución hacia adelante y la ejecución de compensación hacia atrás resultante:
Implementaciones prácticas de código
Exploremos ejemplos de implementación listos para producción tanto para coreografía (en Go) como para orquestación (en Java Spring Boot).
Implementación 1: Saga basada en coreografía en Go
En este ejemplo de Go, demostramos un Servicio de pedidos que maneja la creación de pedidos y escucha eventos de fallas de pago a través de un corredor de eventos para ejecutar una lógica de reversión de compensación.
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)
}
Implementación 2: Saga basada en orquestaciones en Java (Spring Boot)
En este ejemplo de Java, construimos un Saga Orchestrator utilizando un patrón de máquina de estados para coordinar comandos de avance y ejecutar reversiones de compensación cuando falla un paso posterior.
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);
}
}
Conceptos básicos de producción: emparejamiento de Saga con el patrón de bandeja de salida transaccional
Tanto en coreografía como en orquestación, ejecutar una transacción local ($T_i$) requiere publicar un evento de dominio o un comando a través de la red.
Si su servicio actualiza su base de datos SQL y luego publica un mensaje en Kafka, un problema de red después de la confirmación de la base de datos provoca una pérdida de eventos silenciosa. Por el contrario, publicar el mensaje antes de la confirmación de la base de datos genera un procesamiento de eventos fantasma.
Para resolver esto, Sagas debe estar emparejado con el Patrón de bandeja de salida transaccional:
[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]
Al persistir la carga útil del evento en una tabla outbox dentro de la misma transacción de base de datos local, se garantiza la atomicidad. Un proceso en segundo plano asincrónico (como Debezium o un relevo de sondeo) lee desde la tabla de la bandeja de salida y publica eventos en Kafka de manera confiable.
Además, cada consumidor de servicios posteriores debe implementar Idempotencia (usando idempotency_key único o encabezados de deduplicación de mensajes) para que las entregas de mensajes duplicados durante los reintentos no generen cargos duplicados o asignaciones de inventario.
Lista de verificación de decisiones arquitectónicas
Utilice esta práctica matriz de decisiones al diseñar transacciones distribuidas para aplicaciones de microservicios:
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)
Conclusión y conclusiones clave
El Patrón Saga es un patrón arquitectónico esencial para gestionar transacciones distribuidas a través de los límites de los microservicios sin bloquear recursos ni sacrificar la disponibilidad del sistema.
Lista de verificación resumida:
- Abandonar 2PC/XA en microservicios nativos de la nube: el compromiso en dos fases provoca bloqueos estrictos, alta latencia y graves cuellos de botella en la disponibilidad.
- Dividir transacciones en pasos locales: Divida las operaciones globales en transacciones locales ($T_1 \dots T_n$) junto con transacciones de compensación inversas ($C_1 \dots C_{n-1}$).
- Elija el estilo arquitectónico adecuado:
- Utilice Coreografía para flujos sencillos de 2 a 3 pasos impulsados por eventos con acoplamiento flexible.
- Utilice Orquestación para flujos de trabajo empresariales complejos que requieran visibilidad centralizada, bifurcaciones y seguimiento de máquinas de estado.
- Implemente contramedidas de aislamiento: Protéjase contra lecturas sucias y actualizaciones perdidas mediante el uso de bloqueos semánticos (indicadores
PENDING) y bloqueo optimista de relectura. - Garantice mensajería confiable: empareje siempre las implementaciones de Saga con el Patrón de bandeja de salida transaccional y aplique Consumidores idempotentes para manejar los reintentos de forma segura.
Al implementar cuidadosamente el patrón Saga, puede crear microservicios escalables y de alta disponibilidad que permanezcan resistentes y consistentes incluso cuando las redes fallan.