Шаблон «Сага»: распределенные транзакции в архитектуре микросервисов
В традиционных монолитных приложениях поддерживать согласованность данных между несколькими объектами не составляет труда. Ядра реляционных баз данных предоставляют гарантии ACID (атомарность, согласованность, изоляция, долговечность), заключенные в локальные транзакции SQL. Если размещение заказа, удержание платежа или резервирование запасов завершаются неудачей на полпути, вызов ROLLBACK мгновенно отменяет все изменения в базе данных.
Однако при переходе на современную архитектуру микросервисов управление данными существенно меняется. Чтобы обеспечить автономию домена и независимую масштабируемость, каждый микросервис владеет собственной базой данных. Одна бизнес-операция, такая как обработка заказа электронной коммерции, теперь охватывает несколько границ служб и механизмов баз данных (например, PostgreSQL для заказов, DynamoDB для платежей, Redis для инвентаризации).
Поскольку распределенные микросервисы не могут полагаться на одну транзакцию базы данных, поддержание согласованности данных в пределах границ сети становится одной из самых сложных проблем в разработке распределенных систем.
Чтобы решить эту проблему, не жертвуя доступностью или производительностью системы, архитекторы программного обеспечения полагаются на Saga Pattern.
В этом глубоком погружении мы исследуем, почему традиционные распределенные транзакции терпят неудачу, разберем базовую механику шаблона Saga, сравним Хореографию и оркестровку, проанализируем контрмеры изоляции, изучим реализации производственного кода в Go и Java и научимся безопасно обрабатывать откаты при реальных сбоях.
Основная проблема: почему двухфазная фиксация (2PC) не работает в микросервисах
Прежде чем принять шаблон Saga, инженеры часто задаются вопросом: Почему мы не можем использовать традиционную двухфазную фиксацию (2PC/XA) в наших микросервисах?
Механика 2PC
Двухфазная фиксация координирует распределенную транзакцию между несколькими узлами базы данных с помощью центрального диспетчера транзакций в два этапа:
- Фаза подготовки: Менеджер транзакций просит все участвующие узлы базы данных подготовить и заблокировать необходимые строки. Участники голосуют за
YESилиNO. - Фаза фиксации: если все участники проголосовали за
YES, менеджер выдает командуCOMMITвсем узлам. Если какой-либо узел проголосовал заNOили истекло время ожидания, он выдаетROLLBACK.
Client ----> Transaction Manager
|
+-------------+-------------+
| (Prepare) | (Prepare) | (Prepare)
v v v
Order DB Payment DB Inventory DB
Почему 2PC — это антишаблон для микросервисов
Хотя 2PC гарантирует строгую согласованность, в облачных средах микросервисов он не работает из-за нескольких архитектурных недостатков:
- Блокировка и конфликт ресурсов: строки базы данных остаются заблокированными на протяжении всего многоэтапного сетевого установления связи. Если задержка в сети увеличивается или работа службы замедляется, блокировки остаются открытыми, быстро занимая пулы подключений и вызывая каскадный сбой системы.
- Узкое место в доступности. В 2PC доступность системы ограничена произведением доступности всех участников ($A_{total} = A_1 \times A_2 \times \dots \times A_n$). Если какой-либо отдельный узел службы или базы данных отключится от сети на этапе подготовки, вся глобальная транзакция блокируется на неопределенный срок.
- Потеря автономии служб: 2PC вынуждает службы предоставлять протоколы XA уровня базы данных через сетевые API, напрямую связывая свои механизмы баз данных.
- Ограничения брокеров. Брокеры сообщений с высокой пропускной способностью, такие как Apache Kafka или RabbitMQ, изначально не участвуют в традиционных транзакциях XA 2PC в реляционных базах данных.
Согласно Теореме CAP, распределенные системы должны выбирать между строгой согласованностью (C) и высокой доступностью (A) в разделе сетевых разделов (P). Современные системы микросервисов отдают приоритет Доступности и устойчивости к разделению, обменивая мгновенную согласованность на Эвентуальную согласованность (БАЗА: Базовая доступность, Мягкое состояние, Итоговая согласованность).
Что такое шаблон саги?
Шаблон Saga был первоначально предложен Гектором Гарсиа-Молиной и Кеннетом Салемом в 1987 году как механизм обработки долгоживущих транзакций в системах управления базами данных. В современных микросервисах Сага представляет собой последовательность дискретных локальных транзакций.
Вместо того, чтобы заключать весь мультисервисный поток в одну глобальную блокировку, Saga выполняет бизнес-процесс как серию независимых транзакций локальной базы данных ($T_1, T_2, \dots, T_n$).
Каждая локальная транзакция обновляет данные в базе данных одного микросервиса и публикует событие или сообщение домена. Это событие запускает следующую локальную транзакцию ($T_{i+1}$) в нижестоящей службе.
[Order Service] [Payment Service] [Inventory Service]
Local Tx T1 -------------> Local Tx T2 -------------> Local Tx T3
(Create Order) (Process Payment) (Reserve Stock)
Прямое выполнение и обратная компенсация
Если все локальные транзакции завершаются успешно, Saga завершается успешно (прямое выполнение).
Однако если локальная транзакция завершается неудачно на полпути (например, $T_3$ завершается неудачно из-за отсутствия товара на складе), Saga не может просто вызвать базу данных ROLLBACK для предыдущих шагов ($T_1, T_2$), поскольку эти локальные транзакции уже зафиксированы в соответствующих базах данных.
Чтобы отменить уже зафиксированные локальные транзакции, Saga должна выполнить Компенсирующие транзакции ($C_{n-1}, \dots, C_1$) в обратном порядке.
Forward Flow: T1 (Create Order) ---> T2 (Charge Card) ---> T3 (Reserve Inventory - FAILS)
|
Backward Rollback: C1 (Cancel Order) <--- C2 (Refund Card) <----------+
Классификация транзакций Saga
Чтобы разработать надежный рабочий процесс Saga, каждый шаг в последовательности транзакций должен быть отнесен к одному из трех структурных типов:
| Тип транзакции | Описание | Требование идемпотентности и отката |
|---|---|---|
| Компенсационные сделки | Шаги, выполненные до точки невозврата. Их можно отменить или отменить, если на следующем этапе произошел сбой. | Должна быть соответствующая компенсирующая транзакция ($C_i$). |
| Сводная сделка | Решающий шаг в саге. Если поворот окажется успешным, сага гарантированно завершится. Если это не удается, Сага откатывается. | Ни компенсируемому, ни возвратному; он отмечает границу между откатом и завершением вперед. |
| Повторные транзакции | Шаги, выполняемые после транзакции Pivot. В конечном итоге они гарантированно добьются успеха и не требуют компенсации. | Должны быть строго идемпотентными, поскольку попытки будут повторяться автоматически до успешного завершения. |
Пример проверки электронной коммерции: разбивка
Рассмотрим оформление заказа в электронной коммерции, состоящее из четырех шагов:
- $T_1$: Создать отложенный ордер (Компенсируемый) $\to$ Отменить через $C_1$: Отменить ордер.
- $T_2$: Авторизовать платеж (Компенсируемый) $\to$ Отмена через $C_2$: Возврат платежа.
- $T_3$: Резервный запас (Основная транзакция) $\to$ Если распределение запасов прошло успешно, заказ завершается. В случае неудачи активируйте $C_2$ и $C_1$.
- $T_4$: Запрос на отправку (Повторно) $\to$ Выполняется после поворота; повторял попытку до доставки.
Архитектурные стили Saga: хореография против оркестровки
Существует два основных архитектурных стиля для реализации шаблона Saga в распределенных системах: Хореография (децентрализованная) и Оркестровка (централизованная).
Стиль 1: Хореография (децентрализация, управляемая событиями)
В Саге, основанной на хореографии, нет центрального контролера или координатора. Вместо этого микросервисы взаимодействуют асинхронно, прослушивая события домена, опубликованные в центральной шине событий (например, Apache Kafka, NATS или RabbitMQ).
Механика рабочего процесса
- Служба заказов выполняет $T_1$ (создает отложенный ордер) и отправляет событие
OrderCreatedв Kafka. - Платежный сервис слушает
OrderCreated, выполняет $T_2$ (списание средств с кредитной карты) и генерирует событиеPaymentCompleted(илиPaymentFailed). - Служба инвентаризации слушает
PaymentCompleted, выполняет $T_3$ (резервирует элементы). Если товара нет в наличии, выдаетсяInventoryReservationFailed. - Платежный сервис слушает
InventoryReservationFailedи выполняет $C_2$ (выдает возврат средств). - Служба заказов слушает
PaymentRefundedи выполняет $C_1$ (помечает заказ как отмененный).
Преимущества хореографии
- Слабая связь: сервисы подписываются только на темы событий; они не знают о других реализациях сервисов.
- Высокая пропускная способность и децентрализация: прямая публикация/подписка событий устраняет узкие места центрального оркестратора.
- Простота для коротких рабочих процессов: легко настроить для простых 2–3-этапных рабочих процессов.
Недостатки хореографии
- Риск циклической зависимости: службы могут в конечном итоге прослушивать события друг друга, создавая сложные циклические зависимости.
- Сложность отслеживания кода. Понимание сквозного бизнес-процесса требует отслеживания логики в нескольких репозиториях кодовой базы. – Шторм событий и сложность: по мере увеличения количества шагов (например, более 10 сервисов) управление пограничными случаями ошибок приводит к взрывному росту состояния событий.
Стиль 2: оркестровка (централизованное управление рабочими процессами)
В Saga на основе оркестрации выделенный микросервис, известный как Saga Orchestrator, управляет всем жизненным циклом распределенной транзакции. Оркестратор действует как центральный координатор, выдавая явные команды участвующим микросервисам и прослушивая их ответные события.
Механика рабочего процесса
- Клиент отправляет запрос заказа в Saga Orchestrator.
- Оркестратор отправляет команду
CreateOrderв Заказать услугу. Служба заказов возвращаетOrderCreated. - Оркестратор обновляет свой конечный автомат и отправляет команду
ProcessPaymentв Платежный сервис. Платежный сервис возвращаетPaymentSuccessful. - Оркестратор отправляет команду
ReserveInventoryв Inventory Service. Служба инвентаризации возвращаетInventoryFailed (Out of Stock). - Оркестратор обнаруживает сбой, инициирует поток компенсации:
- Отправляет команду
RefundPaymentв Платежный сервис. - Отправляет команду
CancelOrderв Заказать услугу.
- Отправляет команду
- Оркестратор помечает выполнение Saga как
FAILED.
Преимущества оркестрации
- Централизованная бизнес-логика: состояние рабочего процесса и бизнес-логика локализуются в одной службе оркестратора или конечном автомате.
- Нет циклических зависимостей: микросервисы отвечают на команды оркестратора; они не зависят от других нижестоящих услуг и не знают о них.
- Четкий мониторинг и отладка: состояние сквозной транзакции явно сохраняется в хранилище состояний оркестратора (например, PostgreSQL или механизмах рабочих процессов, таких как Temporal/Camunda).
- Упрощенная обработка ошибок: добавление новых шагов или изменение правил отката полностью управляется внутри оркестратора.
Недостатки оркестрации
- Сложность оркестратора: риск размещения слишком большого количества доменной логики в оркестраторе, превращая его в антишаблон «умный оркестратор, тупой сервис».
- Потенциальная единая точка отказа: Оркестратор должен быть обеспечен высокой доступностью и отслеживанием состояния.
Сравнительная матрица: хореография против оркестровки
| Особенность | Хореография | Оркестровка |
|---|---|---|
| Структура управления | Децентрализованный (Event Pub/Sub) | Централизованный (координатор саги/конечный автомат) |
| Соединение | Чрезвычайно низкий (сервисы потребляют события) | Средний (Сервисы принимают команды от Координатора) |
| Видимость процесса | Низкий (распределяется по файлам журналов) | Высокий (единое хранилище состояний визуализирует рабочий процесс) |
| Лучше всего подходит для | Простые рабочие процессы (от 2 до 4 этапов обслуживания) | Сложные рабочие процессы предприятия (5+ шагов, логика ветвления) |
| Инструменты/фреймворки | Кафка, RabbitMQ, NATS, AWS EventBridge | Temporal.io, Шаговые функции AWS, Camunda, Axon |
Проблемы изоляции и меры противодействия (обращение с «кислотой минус I»)
Поскольку локальные транзакции в Saga немедленно фиксируются в своих локальных базах данных, в шаблоне Saga отсутствует изоляция (I) по сравнению с традиционными гарантиями ACID.
Если клиент читает строку базы данных, измененную $T_1$, пока Saga все еще работает, он читает незафиксированное, промежуточное состояние. Если на следующем этапе происходит сбой и активируется компенсация ($C_1$), клиент выполнил грязное чтение.
Распространенные аномалии, вызванные отсутствием изоляции
- Утеряны обновления: Saga A обновляет запись. Прежде чем Сага А завершится, Сага Б перезапишет ту же запись. Если Saga A завершается сбоем и выполняет компенсацию, обновление Saga B перезаписывается.
- Грязное чтение: клиент читает доступные запасы, обновленные Saga A ($T_1$). Сага А выходит из строя ($T_3$) и восстанавливает запасы ($C_1$), но клиент уже разместил заказ на основе устаревших данных.
- Неповторяющееся чтение: служба считывает данные на этапе $T_1$ и считывает их снова на этапе $T_3$, но другая параллельная Saga изменила данные между ними.
Контрмеры и стратегии смягчения последствий
Чтобы поддерживать целостность данных, несмотря на отсутствие изоляции, архитекторы программного обеспечения реализуют определенные шаблоны проектирования изоляции:
1. Семантическая блокировка (ожидание/состояние флага)
Когда локальная транзакция $T_1$ обновляет запись базы данных, она устанавливает поле статуса в PENDING или APPROVAL_REQUIRED (например, ORDER_PENDING_PAYMENT).
Другие саги, одновременно читающие эту запись, должны проверить флаг семантической блокировки и заблокировать или изменить свое поведение до тех пор, пока состояние не изменится на COMMITTED или CANCELLED.
2. Отправка заказа
Спроектируйте последовательность локальных транзакций так, чтобы операции с высоким риском или необратимые события происходили на поздних стадиях выполнения Saga, что сводит к минимуму окно уязвимости.
3. Повторное чтение проверки (оптимистическое управление параллелизмом)
Прежде чем выполнять критический шаг или компенсацию, перечитайте запись целевой базы данных и проверьте временные метки версии (version_id), чтобы убедиться в отсутствии одновременных изменений.
4. Пессимистический взгляд
Измените порядок шагов саги, чтобы минимизировать экономические риски (например, разместите авторизацию платежа как можно ближе к основной транзакции).
Последовательность операций производства: компенсирующие транзакции
Ниже приведена полная блок-схема последовательности операций, иллюстрирующая сбой прямого выполнения и последующее выполнение обратной компенсации:
Практическая реализация кода
Давайте рассмотрим готовые к использованию примеры реализации хореографии (в Go) и оркестрации (в Java Spring Boot).
Реализация 1: Сага на основе хореографии в Go
В этом примере Go мы демонстрируем Службу заказов, обрабатывающую создание заказов и прослушивающую события сбоя платежа через брокера событий для выполнения компенсирующей логики отката.
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)
}
Реализация 2: Сага на основе оркестрации в Java (Spring Boot)
В этом примере Java мы создаем Saga Orchestrator, используя шаблон конечного автомата для координации прямых команд и выполнения компенсирующих откатов в случае сбоя последующего шага.
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);
}
}
Основы производства: сочетание Saga с шаблоном транзакционных исходящих сообщений
Как в хореографии, так и в оркестровке выполнение локальной транзакции ($T_i$) требует публикации события или команды домена по сети.
Если ваш сервис обновляет свою базу данных SQL, а затем публикует сообщение в Kafka, сбой в сети после фиксации базы данных приводит к тихой потере событий. И наоборот, публикация сообщения до фиксации базы данных приводит к обработке фантомных событий.
Чтобы решить эту проблему, Sagas необходимо объединить с Шаблоном транзакционных исходящих сообщений:
[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]
Сохраняя полезные данные события в таблице outbox внутри той же транзакции локальной базы данных, атомарность гарантируется. Асинхронный фоновый процесс (например, Debezium или реле опроса) считывает данные из таблицы исходящих сообщений и надежно публикует события в Kafka.
Кроме того, каждый нижестоящий потребитель услуг должен реализовать Идемпотентность (с использованием уникальных заголовков idempotency_key или дедупликации сообщений), чтобы дублированные доставки сообщений во время повторных попыток не приводили к повторным расходам или распределению ресурсов.
Контрольный список архитектурного решения
Используйте эту практическую матрицу решений при разработке распределенных транзакций для приложений микросервисов:
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)
Заключение и ключевые выводы
Шаблон Saga — это важный архитектурный шаблон для управления распределенными транзакциями за пределами границ микросервисов без блокировки ресурсов или ущерба доступности системы.
Сводный контрольный список:
- Отказ от 2PC/XA в облачных микросервисах: двухфазная фиксация приводит к жесткой блокировке, высокой задержке и серьезным проблемам с доступностью.
- Разбейте транзакции на локальные шаги. Разделите глобальные операции на локальные транзакции ($T_1 \dots T_n$) в сочетании с обратными компенсирующими транзакциями ($C_1 \dots C_{n-1}$).
- Выберите правильный архитектурный стиль:
– Используйте Хореографию для простых 2–3-шаговых потоков, управляемых событиями, со слабой связью.
- Используйте Оркестрацию для сложных бизнес-процессов, требующих централизованной видимости, ветвления и отслеживания конечных автоматов.
- Внедрите меры по изоляции. Защитите себя от несанкционированного чтения и потери обновлений с помощью семантических блокировок (флаги
PENDING) и оптимистической блокировки повторного чтения. - Гарантируйте надежный обмен сообщениями. Всегда сочетайте реализации Saga с шаблоном транзакционных исходящих сообщений и применяйте идемпотентных потребителей для безопасной обработки повторных попыток.
Вдумчиво реализуя шаблон Saga, вы можете создавать масштабируемые микросервисы с высокой доступностью, которые остаются устойчивыми и согласованными даже в случае сбоя сети.