Реалізація Saga Pattern для розподілених транзакцій

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація Saga Pattern для розподілених транзакцій
Складна
~2-4 тижні
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація Saga Pattern для розподілених транзакцій

У архітектурі мікросервісів неможливо використовувати ACID-транзакції крізь межі сервісів. Saga Pattern вирішує задачу розподіленої узгодженості: бізнес-транзакція розбивається на послідовність локальних транзакцій у кожному сервісі, а при збої виконуються компенсуючі транзакції для відкату.

Два види Saga

Хореографія (Choreography) — сервіси реагують на події один одного без центрального координатора:

OrderService        InventoryService     PaymentService      ShippingService
     │                    │                    │                    │
     │── OrderCreated ───►│                    │                    │
     │                    │── StockReserved ──►│                    │
     │                    │                    │── PaymentProcessed►│
     │                    │                    │                    │── ShipmentCreated
     │                    │                    │                    │
     │  (при помилці платежу)                  │                    │
     │                    │◄── StockReleased ──│                    │

Оркестрація (Orchestration) — центральний Saga Orchestrator явно управляє кроками:

class CreateOrderSaga {
  async execute(context: OrderSagaContext): Promise<void> {
    try {
      // Крок 1: Зарезервувати товар
      const reservation = await this.inventoryService.reserveStock(
        context.orderId, context.items
      );
      context.reservationId = reservation.id;

      // Крок 2: Списати оплату
      const payment = await this.paymentService.charge(
        context.customerId, context.totalAmount
      );
      context.paymentId = payment.id;

      // Крок 3: Створити відправку
      await this.shippingService.createShipment(
        context.orderId, context.shippingAddress
      );

      // Крок 4: Підтвердити замовлення
      await this.orderService.confirmOrder(context.orderId);

    } catch (error) {
      await this.compensate(context, error);
      throw new SagaFailedError(context.orderId, error);
    }
  }

  async compensate(context: OrderSagaContext, failedAt: Error): Promise<void> {
    // Компенсації виконуються у зворотному порядку
    if (context.paymentId) {
      await this.paymentService.refund(context.paymentId)
        .catch(e => this.logger.error('Refund failed', e));
    }

    if (context.reservationId) {
      await this.inventoryService.releaseReservation(context.reservationId)
        .catch(e => this.logger.error('Release failed', e));
    }

    await this.orderService.cancelOrder(context.orderId, 'Saga compensation');
  }
}

Персистентна Saga зі станом

Saga повинна пережити перезавантаження сервісу. Стан зберігається в БД:

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;
  }

  async resumeSaga(sagaId: string): Promise<void> {
    const state = await this.sagaRepo.findById(sagaId);
    if (!state || state.status !== 'running') return;

    // Відновлюємо з незавершеного кроку
    await this.executeSaga(sagaId, state.currentStep);
  }
}

Temporal.io для оркестрації саг

Temporal — production-ready рушій для довгоживучих workflow (включаючи саги):

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;
  }
}

Temporal автоматично повторює activity, зберігає історію виконання, дозволяє інспектувати та налагоджувати workflow через UI.

Хореографія через Kafka

// 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 {
      // Публікуємо невдачу — Order Service відкатиться
      await kafka.producer.send({
        topic: 'inventory.events',
        messages: [{ key: event.orderId, value: JSON.stringify({
          type: 'StockReservationFailed', orderId: event.orderId
        })}]
      });
    }
  }
});

Ідемпотентність — обов'язкова вимога

Кожна операція в саге повинна бути ідемпотентною: повторний виклик не створює дублікати.

async function reserveStock(orderId: string, items: Item[]): Promise<Reservation> {
  // Перевіряємо, чи не було створено резервацію для цього замовлення
  const existing = await reservationRepo.findByOrderId(orderId);
  if (existing) return existing;  // ідемпотентно

  return reservationRepo.create({ orderId, items, status: 'reserved' });
}

Терміни реалізації

  • Saga з оркестрацією (2–3 сервіси, без Temporal) — 1–2 тижні
  • Saga з Temporal + моніторинг станів — 2–3 тижні
  • Хореографія через Kafka з ідемпотентними обробниками — 2–4 тижні