JavaRush /Курсы /Модуль 5. Spring /Лекция 212: Введение в паттерн саг (Saga)

Лекция 212: Введение в паттерн саг (Saga)

Модуль 5. Spring
22 уровень , 1 лекция
Открыта

Saga в мире распределённых систем — это паттерн, который помогает управлять долгоживущими транзакциями (теми самыми, которые происходят у нескольких микросервисов в одном процессе). Если упрощенно, это цепочка последовательных шагов, выполняющая локальные транзакции в разных сервисах, обеспечивая их атомарность через компенсационные действия.

Для монолита транзакции ACID (Atomicity, Consistency, Isolation, Durability) — привычное дело. Но в микросервисной архитектуре это, к сожалению, как квадратные колёса на велосипеде: вроде что-то едет, но не так, как хотелось бы. Вот здесь на арену выходит Saga: она поддерживает eventual consistency (постепенную консистентность), которая более реалистична в мире микросервисов.


История и причины появления

Паттерн Saga впервые появился в 1987 году благодаря работам Гектора Гарсиа-Молины и Кеннета Б. Кима. Его основная идея заключалась в возможности разбиения долгоживущей транзакции на серию управляемых операций с возможностью компенсации. Звучит сложно? Ну, представьте ресторан, который принимает заказ (локальная транзакция), но если ингредиенты для блюда закончились, вам предлагают отменить заказ или выбрать другое блюдо (компенсационное действие).

Когда микросервисы стали мейнстримом, Saga вернулась как способ координации распределённых транзакций. И фреймворк Spring Boot, кстати, предоставляет множество инструментов для её реализации.


Как работает Saga?

Saga делится на несколько шагов. Они выполняются последовательно или параллельно, и каждый шаг включен в локальную транзакцию. Суть проста:

  1. Начинается первый шаг (например, резервирование товара).
  2. Если он успешен, переходим ко второму шагу (скажем, списание денег).
  3. Если на каком-то шаге произошёл сбой (ой, денег на карте нет), срабатывают компенсационные транзакции (отмена резервирования товара).

Паттерн Saga фокусируется на двух вещах: основные действия (например, покупка билета) и компенсационные действия (возврат денег, если билет не куплен).

Примеры использования

Пример 1. Бронирование поездки

  • Вы бронируете рейс, гостиницу и аренду автомобиля.
  • Если бронь гостиницы не удалась, нужно отменить бронирование рейса и аренду автомобиля.

Пример 2. Покупка товара в интернет-магазине

  • Уменьшение остатков на складе.
  • Списание средств с карты.
  • Отправка уведомления на email.
  • Если какой-то шаг не удался, нужно вернуть деньги и отменить заказ.

Преимущества Saga

Saga помогает поддерживать баланс между гибкостью распределённой системы и необходимостью сохранять последовательность данных. Вот несколько причин, почему она важна:

  1. Консистентность данных: Saga решает проблему, когда система должна сохранить данные синхронизации, несмотря на ошибки.
  2. Гибкость: Saga работает как для синхронных, так и для асинхронных вызовов.
  3. Масштабируемость: у каждого сервиса есть своё локальное состояние, не зависящее от транзакционного менеджера.
  4. Простота восстановления: если что-то пошло не так, достаточно запустить компенсационные действия.

Ещё один пример

Представим, что ваш интернет-магазин устроен на базе микросервисов с помощью Saga:

  • Товарный сервис: резервирует товар.
  • Сервис оплаты: списывает деньги.
  • Сервис доставки: генерирует заказ для курьера.

Покупатель добавляет товар в корзину, оплачивает, ждёт доставку, всё идёт гладко… пока курьер не сказал: "Извините, адреса доставки не существует". Saga тут спасает: сервис доставки говорит товарному сервису вернуть товар, а платёжному — вернуть деньги. Всё, клиент доволен.


Ограничения?

Saga, конечно, потрясающая, но никто не идеален. Вот что стоит учесть:

  • Нет мгновенной консистентности: Saga обеспечивает eventual consistency, что может быть неприемлемо для критически важных данных.
  • Сложность реализации: чем больше шагов в бизнес-процессе, тем сложнее управлять сагой.
  • Компенсационные действия: их часто трудно проектировать и тестировать (забавный факт: отменить "доставку пиццы" легче, чем отменить "запуск ракеты").

Пример использования паттерна Saga в коде

Для наглядности давайте реализуем базовый сценарий: бронирование товара в интернет-магазине, оплата и доставка. Наш процесс будет выглядеть так:

  1. Резервирование товара.
  2. Оплата заказа.
  3. Генерация доставки.

Шаг 1: Определим события


public class ReserveProductCommand {
    private String productId;
    private int quantity;
    private String orderId;
    // Конструкторы, геттеры и сеттеры
}

public class PaymentCommand {
    private String orderId;
    private double amount;
    // Конструкторы, геттеры и сеттеры
}

public class ShipOrderCommand {
    private String orderId;
    private String address;
    // Конструкторы, геттеры и сеттеры
}

Шаг 2: Резервирование товара


@Service
public class InventoryService {

    public void reserveProduct(ReserveProductCommand command) {
        // Логика резервирования товара
        System.out.println("Резервируем продукт " + command.getProductId());
    }

    public void cancelReservation(String productId) {
        // Компенсационная транзакция
        System.out.println("Отмена резервирования продукта " + productId);
    }
}

Шаг 3: Оплата товара


@Service
public class PaymentService {

    public void processPayment(PaymentCommand command) {
        // Логика списания средств
        System.out.println("Списываем " + command.getAmount() + " для заказа " + command.getOrderId());
    }

    public void refundPayment(String orderId) {
        // Компенсационная транзакция
        System.out.println("Возврат средств для заказа " + orderId);
    }
}

Шаг 4: Организация 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 {
            // Шаг 1: Резервирование товара
            inventoryService.reserveProduct(new ReserveProductCommand(productId, quantity, orderId));

            // Шаг 2: Оплата
            paymentService.processPayment(new PaymentCommand(orderId, amount));

            // Шаг 3: Генерация доставки
            System.out.println("Генерация доставки для заказа: " + orderId);

        } catch (Exception ex) {
            // Обработка ошибок и запуск компенсаций
            inventoryService.cancelReservation(productId);
            paymentService.refundPayment(orderId);
        }
    }
}

Этот простой пример иллюстрирует базовый процесс реализации Saga: вызов сервисов, обработка ошибок и выполнение компенсационных действий.

Бонус: реализация может быть значительно упрощена и систематизирована с использованием Spring State Machine, Axon Framework или других инструментов.


С этим фундаментом вы уже готовы двигаться дальше — к обсуждению оркестрации и хореографии Saga, которые позволяют ещё проще и элегантнее организовывать сложные бизнес-процессы.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ