Питання 1 · Розділ 17

Що таке патерн Saga і коли його використовувати

У моноліті ACID-транзакція гарантує, що всі зміни або збережуться, або відкотяться разом. У мікросервісах дані розкидані по різних БД, і немає механізму, який атомарно змінить у...

Мовні версії: English Russian Ukrainian

🟢 Junior Level

Saga — це патерн для керування розподіленими транзакціями в мікросервісах.

У моноліті ACID-транзакція гарантує, що всі зміни або збережуться, або відкотяться разом. У мікросервісах дані розкидані по різних БД, і немає механізму, який атомарно змінить усе одразу.

У моноліті є одна база даних і звичайні транзакції (ACID). У мікросервісах у кожного сервісу своя БД, і неможливо зробити одну транзакцію на кілька сервісів.

Saga вирішує цю проблему:

  • Розбиває велику транзакцію на послідовність локальних транзакцій
  • Кожна локальна транзакція — у своєму сервісі
  • Якщо щось пішло не так — виконуються компенсувальні транзакції (відкат)
Приклад — замовлення в інтернет-магазині:
1. Order Service: створити замовлення
2. Payment Service: списати гроші
3. Inventory Service: зарезервувати товар
4. Shipping Service: відправити

Якщо Payment впав → скасувати замовлення (компенсація)
Якщо Inventory впав → повернути гроші + скасувати замовлення
// Що бачить користувач: замовлення створено → оплату пройдено → інвентар зарезервовано.
// Якщо Shipping впав: compensation скасовує все, користувач бачить "замовлення скасовано".
// Eventual consistency зазвичай займає секунди-хвилини.

🟡 Middle Level

Два типи Saga

1. Choreography (Хореографія):

Кожен сервіс публікує подію → інші сервіси слухають і реагують

Order Service → "OrderCreated" → Payment Service
Payment Service → "PaymentCompleted" → Inventory Service
Inventory Service → "InventoryReserved" → Shipping Service

2. Orchestration (Оркестрація):

Центральний оркестратор керує всім процесом

        Orchestrator
       /    |    \     \
  Order  Payment  Inventory  Shipping

Коли використовувати

✅ Saga підходить коли:

  • Транзакція залучає кілька сервісів
  • Потрібна eventual consistency (не миттєва узгодженість)
  • Немає єдиної БД

❌ Якщо бізнес-вимоги юридично вимагають строгої ACID (банківські перекази), Saga може не підійти без додаткових гарантій (блокування, двофазний комміт).

Типові помилки

  1. Забута компенсація:
    Order → Payment → Inventory (fail)
    Потрібно: refund(payment) + cancel(order)
    Якщо забули refund → гроші списані, замовлення немає
    

🔴 Senior Level

Internal Implementation

Saga log:

Кожна Saga повинна логувати:
1. Розпочаті кроки
2. Завершені кроки
3. Компенсації

Це дозволяє відновити стан після рестарту

Архітектурні Trade-offs

Хореографія Оркестрація
Немає єдиної точки відмови Оркестратор — single point of failure
Складно відстежити потік Легко моніторити
Циклічні залежності Немає циклічних залежностей
Добре для простих Saga Добре для складних Saga

Edge Cases

1. Idempotency:

Компенсувальна транзакція може викликатись двічі
→ Потрібна ідемпотентність

refund(paymentId):
    if alreadyRefunded(paymentId): return
    doRefund(paymentId)
    markRefunded(paymentId)

2. Partial failure:

Saga: A → B → C (fail)
Компенсації: compensate(B) → compensate(A)

Якщо compensate(B) теж впала → retry + alert

Production Experience

Order Saga:

@SagaDefinition
public class OrderSaga {

    @StartSaga
    @SagaEventHandler(associationProperty = "orderId")
    public void handle(OrderCreatedEvent event) {
        SagaLifecycle.start();
        commandGateway.send(new ReserveInventoryCommand(event.orderId()));
    }

    @SagaEventHandler(associationProperty = "orderId")
    public void handle(InventoryReservedEvent event) {
        commandGateway.send(new ProcessPaymentCommand(event.orderId()));
    }

    @SagaEventHandler(associationProperty = "orderId")
    public void handle(PaymentCompletedEvent event) {
        commandGateway.send(new ShipOrderCommand(event.orderId()));
        SagaLifecycle.end();
    }

    @SagaEventHandler(associationProperty = "orderId")
    public void handle(InventoryReservationFailedEvent event) {
        // Компенсація — скасувати замовлення
        commandGateway.send(new CancelOrderCommand(event.orderId()));
        SagaLifecycle.end();
    }
}

Best Practices

✅ Логуйте кожен крок Saga
✅ Реалізуйте idempotency для компенсацій
✅ Використовуйте timeouts для кожного кроку
✅ Моніторьте затримки між кроками

❌ Не використовуйте для простих CRUD операцій
❌ Не забувайте про компенсувальні транзакції
❌ Не ігноруйте partial failures

🎯 Шпаргалка для співбесіди

Обов’язково знати:

  • Saga — патерн для розподілених транзакцій без єдиної БД
  • Два типи: Choreography (event-driven) та Orchestration (coordinator-driven)
  • Компенсувальні транзакції скасовують кроки у зворотному порядку
  • Eventual consistency — дані узгоджені не миттєво
  • Idempotency критична для компенсацій
  • Transactional Outbox гарантує доставку подій
  • Хореографія — 2-3 сервіси, Оркестрація — 5+ сервісів
  • Saga timeout захищає від транзакцій, що зависли

Часті уточнюючі питання:

  • Чим Saga відрізняється від 2PC? Saga не блокує ресурси, працює з гетерогенними БД, але дає eventual consistency замість строгої ACID.
  • Що буде якщо компенсація теж впаде? Retry + alert для manual intervention.
  • Коли НЕ використовувати Saga? Для простих CRUD-операцій в одному сервісі або коли потрібна строга ACID (банківські перекази).
  • Як моніторити Saga? Логуйте кожен крок, відстежуйте тривалість між кроками, використовуйте distributed tracing.

Червоні прапорці (НЕ говорити):

  • “Saga гарантує ACID” — ні, eventual consistency
  • “Компенсація = ROLLBACK” — ні, це нова транзакція
  • “Хореографія краща для 10 сервісів” — ні, оркестрація
  • “Saga не потрібна якщо є Kafka” — Kafka лише транспорт, логіка все одно потрібна

Пов’язані теми:

  • [[2. У чому різниця між хореографією та оркестрацією в Saga]]
  • [[3. Як реалізувати розподілені транзакції в мікросервісах]]
  • [[4. Що таке компенсувальні транзакції]]
  • [[13. Що таке патерн Database per Service]]
  • [[22. Що таке distributed tracing]]