Что такое saga pattern и event sourcing в NestJS?
Saga Pattern
Saga — паттерн для управления распределёнными транзакциями, которые затрагивают несколько сервисов или агрегатов. Вместо одной большой ACID-транзакции процесс разбивается на последовательность локальных шагов, каждый из которых публикует событие после успешного выполнения. Если один из шагов завершается ошибкой, сага выполняет компенсирующие действия (compensating actions), отменяющие эффект уже выполненных шагов.
Ключевая идея: сага реагирует на события и решает, какую следующую команду отправить дальше.
Как это выглядит в NestJS
Модуль @nestjs/cqrs предоставляет декоратор @Saga(). Он позволяет описать сагу как RxJS-поток: сага подписывается на общий поток событий (Observable<IEvent>) и с помощью операторов filter/map превращает нужные события в новые команды.
Event Sourcing
Event Sourcing — подход к хранению состояния, при котором вместо текущего снимка данных (как в обычной таблице БД) хранится полная последовательность событий, приведших к этому состоянию. Текущее состояние агрегата восстанавливается путём последовательного применения всех событий (replay).
Преимущества:
- полный аудит истории изменений состояния
- возможность восстановить состояние на любой момент времени
- естественная интеграция с CQRS и событийной архитектурой
Недостатки:
- сложность реализации и отладки
- необходимость версионирования событий при изменении схемы
- дополнительные затраты на snapshot'ы для ускорения восстановления состояния при большой истории событий
Как это связано с NestJS
@nestjs/cqrs даёт базовые строительные блоки: EventBus, CommandBus, класс AggregateRoot с методом apply() для генерации событий и EventsHandler для их обработки. Важно понимать: сам модуль не хранит события в базе данных из коробки — он лишь предоставляет инфраструктуру публикации и обработки событий. Полноценный event store нужно подключать отдельно.
Как это работает вместе
Типичный сценарий: агрегат вызывает apply(), генерируя событие → событие публикуется через EventBus → сага подписана на этот поток и, увидев нужное событие, отправляет новую команду через CommandBus → обработчик команды выполняет следующий шаг бизнес-процесса.
Так распределённый процесс (например, оформление заказа: списание денег, резервирование товара, отправка уведомления) реализуется без единой транзакции — согласованность достигается за счёт цепочки событий и компенсирующих действий при сбоях.
Что хочет услышать интервьюер
Кандидат понимает, что saga решает проблему распределённых транзакций без двухфазного коммита
Кандидат знает про компенсирующие действия при ошибке в одном из шагов
Кандидат понимает разницу между хранением снимка состояния и хранением событий
Кандидат знает, что в NestJS для этого используется модуль @nestjs/cqrs (Saga, EventBus, CommandBus)
Кандидат может привести простой пример сценария (например, оформление заказа)
Пример: Пример саги в NestJS с @nestjs/cqrs
import { Injectable } from '@nestjs/common';
import { ICommand, ofType, Saga } from '@nestjs/cqrs';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { OrderCreatedEvent } from './events/order-created.event';
import { ReserveStockCommand } from './commands/reserve-stock.command';
@Injectable()
export class OrderSagas {
// Сага подписывается на поток событий и реагирует на OrderCreatedEvent
@Saga()
orderCreated = (events$: Observable<any>): Observable<ICommand> => {
return events$.pipe(
ofType(OrderCreatedEvent),
// при создании заказа инициируем резервирование товара на складе
map((event) => new ReserveStockCommand(event.orderId, event.items)),
);
};
}
Типичные ошибки
Путают saga pattern с обычной БД-транзакцией и не упоминают компенсирующие действия
Считают, что @nestjs/cqrs сам хранит события в базе данных без дополнительной настройки
Не могут объяснить, зачем вообще нужен replay событий и как восстанавливается текущее состояние
Путают Event Sourcing с обычной публикацией доменных событий без сохранения истории
Не упоминают, что при большом количестве событий нужны snapshot'ы для производительности


