> Как организовать транзакционность в микросервисной архитектуре при работе с разными базами данных (Node.js, JavaScript)

Уровень: senior · Роль: backend · Категория: Технические вопросы

Компании: TrendTech

Стек: Node.js, JavaScript

> Пример ответа

Короткий ответ

Транзакционность в микросервисах с разными БД достигается через паттерн Saga - цепочку локальных транзакций с компенсирующими действиями при сбое. Используются два подхода: хореография (сервисы обмениваются событиями) и оркестрация (центральный координатор управляет шагами). Для Node.js подходят библиотеки вроде @nestjs/cqrs или собственные реализации на основе очередей (RabbitMQ, Kafka). ACID-транзакции здесь неприменимы, вместо них - eventual consistency.

Подробное объяснение

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

Два основных варианта реализации:

  • Хореография: каждый сервис после выполнения своей локальной транзакции публикует событие, которое триггерит следующий шаг. При ошибке публикуется событие отката. Минус - логика размазана по сервисам, сложно отслеживать.
  • Оркестрация: выделенный сервис-оркестратор (например, на основе state machine) управляет последовательностью шагов и вызывает каждый сервис через API или очереди. Проще мониторить и отлаживать, но добавляет единую точку отказа.

Для Node.js типичная реализация использует message broker (RabbitMQ, Kafka) для асинхронной коммуникации и хранения состояния саги в БД оркестратора. Важно учитывать идемпотентность обработчиков и обработку дублирующихся событий.

На практике

При проектировании саги нужно:

  1. Определить бизнес-операцию и разбить её на шаги с чёткими границами транзакций.
  2. Для каждого шага написать компенсирующее действие (например, отмена заказа, возврат средств).
  3. Обеспечить идемпотентность - повторное выполнение того же шага не должно ломать состояние.
  4. Использовать retry с exponential backoff для временных ошибок.
  5. Хранить состояние саги в durable storage (БД) для восстановления после падения оркестратора.

Для Node.js удобно использовать библиотеки: sagas из @nestjs/cqrs, temporal.io для сложных workflows, или написать простую реализацию на основе очередей и state machine (например, через xstate).

Пример кода

JAVASCRIPT
// Пример оркестратора саги на Node.js с использованием RabbitMQ
class OrderSagaOrchestrator {
constructor(channel, db) {
this.channel = channel;
this.db = db; // хранилище состояний саг
}
async start(orderData) {
const sagaId = uuid();
await this.db.save({ sagaId, status: 'PENDING', data: orderData });
await this.channel.publish('order.events', 'order.created', { sagaId, orderData });
}
async handleEvent(event) {
const saga = await this.db.get(event.sagaId);
if (!saga) return;
const nextStep = this.getNextStep(saga.status, event.type);
if (nextStep) {
await this.db.update(saga.sagaId, { status: nextStep.status });
await this.channel.publish('order.commands', nextStep.command, { sagaId: saga.sagaId, data: saga.data });
} else if (event.type === 'error') {
await this.compensate(saga);
}
}
async compensate(saga) {
const compensationSteps = this.getCompensationSteps(saga.status);
for (const step of compensationSteps) {
await this.channel.publish('order.commands', step.command, { sagaId: saga.sagaId, data: saga.data });
}
await this.db.update(saga.sagaId, { status: 'COMPENSATED' });
}
}

Как отвечать на собеседовании

Начни с объяснения, почему ACID-транзакции не подходят для микросервисов - это покажет понимание trade-off. Затем опиши Saga как основной паттерн, упомяни хореографию и оркестрацию, сравни их плюсы и минусы. Приведи пример из практики: как ты реализовывал сагу для заказа в интернет-магазине (резервирование товара, списание денег, отправка). Упомяни идемпотентность и обработку ошибок - это ключевые моменты для senior. Если спросят про альтернативы, скажи про Outbox pattern для гарантированной доставки событий и про двухфазный коммит только в крайнем случае.

Что проверяет интервьюер

  • Понимание ограничений ACID в распределённых системах.
  • Знание паттерна Saga и его вариаций.
  • Умение проектировать компенсирующие транзакции.
  • Практический опыт с message brokers и асинхронной коммуникацией.
  • Внимание к идемпотентности и обработке сбоев.
  • Способность выбирать между хореографией и оркестрацией под конкретную задачу.

Типичные ошибки

  • Попытка использовать распределённые ACID-транзакции (двухфазный коммит) - это антипаттерн для микросервисов.
  • Игнорирование компенсирующих действий - без них система не сможет откатиться при сбое.
  • Отсутствие идемпотентности - повторная обработка события приведёт к дублированию данных.
  • Синхронное выполнение шагов саги - это убивает асинхронность и увеличивает latency.
  • Хранение состояния саги только в памяти - при падении оркестратора все саги потеряются.
  • Смешивание бизнес-логики с инфраструктурной (например, прямой вызов БД другого сервиса).

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью