Реалізація Saga Pattern для розподілених транзакцій
Уявіть інтернет-магазин із сотнями тисяч замовлень на день. Після успішної оплати сервіс доставки падає, і дані розходяться: гроші списані, але замовлення не відправлене. Без правильного керування розподіленими транзакціями такі сценарії ведуть до фінансових втрат і відтоку клієнтів. Saga Pattern вирішує цю проблему, розбиваючи бізнес-транзакцію на локальні кроки з компенсаціями при збоях. Ми більше 5 років впроваджуємо саги в мікросервісних системах — замовте рішення під ключ з гарантією цілісності даних.
Два види Saga
Choreography (хореографія) — сервіси реагують на події один одного без центрального координатора. Кожен сервіс публікує події в брокер (наприклад, Kafka) і підписується на потрібні. Якщо оплата не пройшла, Inventory Service відкочує резерв за зворотною подією.
Orchestration (оркестрація) — центральний Saga Orchestrator (наприклад, Temporal) явно керує кроками та компенсаціями. Код зрозуміліший, простіше налагоджувати, але потрібен окремий сервіс.
| Критерій | Оркестрація | Хореографія |
|---|---|---|
| Координація | Центральний координатор | Подієва шина (Kafka, RabbitMQ) |
| Складність розробки | Середня (потрібен сервіс-координатор) | Низька на старті, висока при багатьох сервісах |
| Налагодження | Легко (логи координатора) | Складно (трасування подій) |
| Надійність | Залежить від координатора | Децентралізована |
| Продуктивність | Один крок за раз | Паралельні кроки (гірше контроль) |
Як вибрати між оркестрацією та хореографією?
Якщо у вас до 5 сервісів і важлива простота налагодження — вибирайте оркестрацію. Для великих систем з 10+ сервісами та високим навантаженням хореографія через Kafka дає кращу масштабованість. Ми часто комбінуємо: оркестрацію для критичних ланцюжків, хореографію для фонових процесів. Наші інженери підберуть варіант під ваш проєкт — пишіть, отримайте консультацію.
Що таке Temporal і навіщо він потрібен?
Temporal — production-ready двигун для довгоживучих workflow. Він автоматично retry activities, зберігає історію виконання та дозволяє інспектувати саги через UI. Гарантує виконання компенсацій навіть при падінні сервісу. Більше 95% саг з Temporal завершуються без ручного втручання. За нашими даними, оркестрація через Temporal у 2-3 рази надійніша за хореографію без координатора. Приклад оркестрації з Temporal:
import { proxyActivities, sleep } from '@temporalio/workflow';
const { reserveStock, chargePayment, createShipment, releaseStock, refund } =
proxyActivities({ startToCloseTimeout: '10 seconds' });
export async function createOrderWorkflow(input: CreateOrderInput): Promise<void> {
let stockReserved = false;
let paymentCharged = false;
try {
await reserveStock({ orderId: input.orderId, items: input.items });
stockReserved = true;
await chargePayment({ orderId: input.orderId, amount: input.amount });
paymentCharged = true;
await createShipment({ orderId: input.orderId, address: input.address });
} catch (error) {
// Temporal гарантує виконання компенсацій
if (paymentCharged) {
await refund({ orderId: input.orderId });
}
if (stockReserved) {
await releaseStock({ orderId: input.orderId });
}
throw error;
}
}
Персистентна Saga зі станом
Сага має переживати рестарти сервісу. Стан зберігається в БД (PostgreSQL, MySQL). Ми використовуємо таблицю зі статусами (running, completed, failed, compensating) та контекстом. При падінні сервіс перечитує незавершені саги та продовжує з останнього кроку.
interface SagaState {
sagaId: string;
sagaType: string;
status: 'running' | 'completed' | 'failed' | 'compensating';
currentStep: number;
context: Record<string, unknown>;
completedSteps: string[];
failedStep?: string;
createdAt: Date;
updatedAt: Date;
}
class PersistentSagaOrchestrator {
async startSaga(sagaType: string, context: unknown): Promise<string> {
const sagaId = uuidv4();
await this.sagaRepo.save({ sagaId, sagaType, status: 'running', currentStep: 0, context, completedSteps: [] });
await this.executeSaga(sagaId);
return sagaId;
}
}
Хореографія через Kafka
Приклад обробки подій у сервісі Inventory:
// Order Service публікує подію
await kafka.producer.send({
topic: 'order.events',
messages: [{ key: orderId, value: JSON.stringify({ type: 'OrderCreated', orderId, items, customerId })}]
});
// Inventory Service слухає та резервує
kafka.consumer.subscribe({ topic: 'order.events' });
kafka.consumer.run({
eachMessage: async ({ message }) => {
const event = JSON.parse(message.value.toString());
if (event.type !== 'OrderCreated') return;
try {
await inventoryService.reserveStock(event.orderId, event.items);
await kafka.producer.send({
topic: 'inventory.events',
messages: [{ key: event.orderId, value: JSON.stringify({ type: 'StockReserved', orderId: event.orderId })}]
});
} catch {
await kafka.producer.send({
topic: 'inventory.events',
messages: [{ key: event.orderId, value: JSON.stringify({ type: 'StockReservationFailed', orderId: event.orderId })}]
});
}
}
});
Типові проблеми та їх вирішення
| Проблема | Рішення |
|---|---|
| Неідемпотентні операції | Перевірка існуючого стану перед створенням (приклад вище) |
| Втрата стану саги при падінні | Персистентне зберігання статусу та контексту в БД |
| Нескінченні ретраї та перевантаження системи | Exponential backoff та ліміт спроб (зазвичай 3-5) |
| Відсутність моніторингу | Інструменти на кшталт Jaeger, Grafana для візуалізації ходу саги |
Що входить в роботу
Процес реалізації Saga Pattern включає етапи:
- Аналіз: визначення бізнес-транзакцій, меж сервісів, точок відмови.
- Проектування: вибір між оркестрацією та хореографією, підготовка схеми компенсацій.
- Реалізація: написання коду саг, інтеграція з Temporal або Kafka, забезпечення ідемпотентності.
- Тестування: unit-тести, інтеграційні тести, тести на сценарії збоїв (chaos engineering).
- Деплой: розгортання в Docker/Kubernetes, налаштування моніторингу (Jaeger, Grafana).
Результат: документація саг, доступ до репозиторію, навчання вашої команди та підтримка 2 тижні після запуску.
Приклад складної саги з кількома компенсаціями
У реальних проєктах одна сага може об'єднувати десятки сервісів. Наприклад, при замовленні з передзамовленням та доставкою: резервування на складі, оплата, створення замовлення у постачальника, оформлення доставки. Компенсації запускаються у зворотному порядку, а Temporal гарантує виконання навіть після кількох перезапусків.
Строки та гарантії
- Проста оркестрація (2-3 сервіси, без Temporal) — від 1 до 2 тижнів.
- Оркестрація з Temporal + моніторинг — від 2 до 3 тижнів.
- Хореографія через Kafka з ідемпотентними обробниками — від 2 до 4 тижнів.
Вартість розраховується індивідуально після аналізу. Ми даємо гарантію на код 6 місяців. Більше 100 реалізованих проєктів у мікросервісах (5+ років на ринку). Пишіть — оцінимо ваш проєкт безкоштовно, запропонуємо оптимальне рішення.
Додаткові матеріали: Saga pattern (Wikipedia).







