Saga, en el mundo de los sistemas distribuidos, es un patrón que ayuda a gestionar transacciones de larga duración (esas mismas que ocurren en varios microservicios dentro de un proceso). Si lo simplificamos, es una cadena de pasos secuenciales que ejecuta transacciones locales en distintos servicios, asegurando su atomicidad mediante acciones compensatorias.
Para un monolito, las transacciones ACID (Atomicity, Consistency, Isolation, Durability) son algo habitual. Pero en una arquitectura de microservicios eso, por desgracia, es como ruedas cuadradas en una bicicleta: más o menos se mueve, pero no como quisiéramos. Aquí entra Saga: soporta eventual consistency, que es más realista en el mundo de los microservicios.
Historia y razones de su aparición
El patrón Saga apareció por primera vez en 1987 gracias a los trabajos de Héctor García‑Molino y Kenneth B. Kim. Su idea principal era dividir una transacción de larga duración en una serie de operaciones gestionables con posibilidad de compensación. ¿Suena complicado? Pues imagina un restaurante que toma tu pedido (transacción local), pero si faltan ingredientes para el plato, te ofrecen cancelar el pedido o elegir otro plato (acción compensatoria).
Cuando los microservicios se volvieron mainstream, Saga volvió como una forma de coordinar transacciones distribuidas. Y por cierto, Spring Boot ofrece varias herramientas para implementarla.
¿Cómo funciona Saga?
Saga se divide en varios pasos. Se pueden ejecutar secuencialmente o en paralelo, y cada paso está envuelto en una transacción local. La idea es sencilla:
- Comienza el primer paso (por ejemplo, reservar un producto).
- Si tiene éxito, pasamos al segundo paso (por ejemplo, debitar el dinero).
- Si en algún paso ocurre un fallo (uy, no hay fondos), se ejecutan transacciones compensatorias (cancelar la reserva del producto).
El patrón Saga se centra en dos cosas: acciones principales (por ejemplo, comprar un billete) y acciones compensatorias (devolver el dinero si no se completa la compra).
Ejemplos de uso
Ejemplo 1. Reserva de un viaje
- Reservas vuelo, hotel y coche de alquiler.
- Si la reserva del hotel falla, hay que cancelar la reserva del vuelo y del coche.
Ejemplo 2. Compra en una tienda online
- Reducir el stock en el almacén.
- Debitar fondos de la tarjeta.
- Enviar una notificación por email.
- Si algún paso falla, hay que devolver el dinero y cancelar el pedido.
Ventajas de Saga
Saga ayuda a equilibrar la flexibilidad de un sistema distribuido con la necesidad de mantener la consistencia eventual de los datos. Aquí tienes algunas razones por las que es importante:
- Consistencia de datos: Saga resuelve el problema de mantener datos coordinados a pesar de errores.
- Flexibilidad: Saga funciona tanto para llamadas síncronas como asíncronas.
- Escalabilidad: cada servicio tiene su propio estado local, sin depender de un transaction manager centralizado.
- Facilidad de recuperación: si algo va mal, basta ejecutar las acciones compensatorias.
Otro ejemplo
Imagina que tu tienda online está basada en microservicios usando Saga:
- Servicio de producto: reserva el producto.
- Servicio de pagos: debita el dinero.
- Servicio de envíos: crea la orden para el mensajero.
El comprador añade el producto al carrito, paga, espera la entrega, todo va bien… hasta que el mensajero dice: "Lo siento, la dirección de entrega no existe". Saga salva la situación: el servicio de envíos indica al servicio de producto que libere el producto, y al servicio de pagos que reembolse el dinero. Cliente contento.
¿Limitaciones?
Saga es fantástica, pero nadie es perfecto. Ten en cuenta lo siguiente:
- No hay consistencia instantánea: Saga garantiza eventual consistency, lo que puede no ser aceptable para datos críticamente sensibles.
- Complejidad de implementación: cuanto más pasos en el proceso de negocio, más difícil es gestionar la saga.
- Acciones compensatorias: a menudo son difíciles de diseñar y probar (dato curioso: deshacer la "entrega de pizza" es más fácil que deshacer el "lanzamiento de un cohete").
Ejemplo de uso del patrón Saga en código
Para ilustrarlo, implementemos un escenario básico: reservar producto en una tienda online, pago y envío. Nuestro proceso será:
- Reserva del producto.
- Pago del pedido.
- Generación del envío.
Paso 1: Definamos los eventos
public class ReserveProductCommand {
private String productId;
private int quantity;
private String orderId;
// Constructores, getters y setters
}
public class PaymentCommand {
private String orderId;
private double amount;
// Constructores, getters y setters
}
public class ShipOrderCommand {
private String orderId;
private String address;
// Constructores, getters y setters
}
Paso 2: Reserva del producto
@Service
public class InventoryService {
public void reserveProduct(ReserveProductCommand command) {
// Lógica de reserva del producto
System.out.println("Reservando producto " + command.getProductId());
}
public void cancelReservation(String productId) {
// Transacción compensatoria
System.out.println("Cancelando reserva del producto " + productId);
}
}
Paso 3: Pago del pedido
@Service
public class PaymentService {
public void processPayment(PaymentCommand command) {
// Lógica de débito de fondos
System.out.println("Debitando " + command.getAmount() + " para el pedido " + command.getOrderId());
}
public void refundPayment(String orderId) {
// Transacción compensatoria
System.out.println("Reembolso para el pedido " + orderId);
}
}
Paso 4: Organización de la Saga
@Service
public class OrderSagaService {
@Autowired
private InventoryService inventoryService;
@Autowired
private PaymentService paymentService;
public void processOrder(String orderId, String productId, int quantity, double amount) {
try {
// Paso 1: Reserva del producto
inventoryService.reserveProduct(new ReserveProductCommand(productId, quantity, orderId));
// Paso 2: Pago
paymentService.processPayment(new PaymentCommand(orderId, amount));
// Paso 3: Generación del envío
System.out.println("Generando envío para el pedido: " + orderId);
} catch (Exception ex) {
// Manejo de errores y ejecución de compensaciones
inventoryService.cancelReservation(productId);
paymentService.refundPayment(orderId);
}
}
}
Este ejemplo simple ilustra el proceso básico de implementar una Saga: llamar servicios, manejar errores y ejecutar acciones compensatorias.
Bonus: la implementación puede simplificarse y sistematizarse mucho usando Spring State Machine, Axon Framework u otras herramientas.
Con estos fundamentos ya puedes avanzar — hacia la discusión sobre orquestación y coreografía de Sagas, que permiten organizar procesos de negocio complejos de forma aún más sencilla y elegante.
GO TO FULL VERSION