Как решать проблемы с транзакциями при изменении нескольких агрегатов в одной транзакции
Что проверяют
Знаете ли вы правило из DDD: агрегат это граница согласованности, и одна транзакция меняет один агрегат. Интервьюер хочет услышать, что вы делаете, когда бизнес всё-таки требует изменить два, и чем за это платите. Следом часто спрашивают про transactional outbox.
Как отвечать
Сначала проверьте, действительно ли инвариант общий. Если два агрегата обязаны меняться строго вместе, возможно, границы проведены неверно и это один агрегат. Если мгновенная согласованность не нужна, варианты такие:
- одна локальная транзакция: оба агрегата в одной базе, и вы осознанно отступаете от правила ради простоты. В Node.js это
prisma.$transaction,dataSource.transaction()в TypeORM,knex.transaction(). С голымpgтранзакция живёт на одном клиенте изpool.connect(), аpool.queryможет отправить каждый запрос в своё соединение, иBEGINокажется не там. Цена: блокировки на двух агрегатах и связность кода; - доменные события: меняете агрегат A и в той же транзакции пишете событие в таблицу outbox. Отдельный процесс публикует его в брокер, обработчик меняет агрегат B. Обработчик обязан быть идемпотентным: событие может прийти дважды;
- сага: цепочка локальных транзакций с компенсирующими действиями, оркестрация через координатор или хореография через события. Компенсация не откат, а новая операция вроде «вернуть деньги»;
- двухфазная фиксация: упомянуть можно, но в Node-сервисах её почти не встретишь. Большинство брокеров и NoSQL-баз её не поддерживают, а упавший координатор держит блокировки.
Для параллельных изменений одного агрегата добавьте оптимистическую блокировку: колонка version и UPDATE ... WHERE version = $n.
Где спотыкаются
Пишут «транзакцию» вручную: await a.update(), await b.update(), а в catch вызывают откат у каждого. Это не атомарно: процесс может упасть между вызовами, а откат сам может не выполниться.
Публикуют событие в брокер после COMMIT отдельным вызовом. При сбое между ними событие теряется, именно эту дыру закрывает outbox.
В MongoDB забывают, что многодокументные транзакции работают только на replica set или шардированном кластере, на одиночном mongod их нет.