Sellec
@Sellec
Кодер

Как поступить в случае вложенных транзакций?

У меня не настолько большой опыт проектирования систем, и, когда столкнулся с ситуацией, возникли сомнения, как её верно разрешить.
Есть таблица Role - роли пользователей.
ПО условно разделено на два уровня - пользовательский код и api (тоже код, но выделенный отдельно как ядро системы).
В апи есть метод AddRoleToUser - передаем в метод идентификатор юзера, идентификатор роли -> метод проверяет, существуют ли роль и пользователь -> в случае успеха назначает роль путем записи еще в парочку второстепенных таблиц, затем рассылает письма по электронке. Запросы внутри AddRoleToUser обернуты в transactionScope (scope1).
Пользовательский код делает следующее:
1. Создает свой transactionScope (scope2) в режиме Requires, Serializable;
2. Пишет данные во второстепенные таблицы таблица1 и таблица2;
3. Пишет напрямую в таблицу Role, создавая там роль.
4. Вызывает AddRoleToUser, передавая ему идентификатор созданной роли и идентификатор какого-то пользователя (заведомо существующего);
5. Выходит из scope2 без commit (то есть происходит rollback).

У меня возникло непонимание, как правильно поступать внутри AddRoleToUser с чтением данных из базы. Могут возникнуть следующие сценарии:
1) scope1 в режиме requires, уровень изоляции любой - роль считывается без проблем, данные записываются, письмо отправляется, но после rollback в scope2 откатываются все записи, созданные в AddRoleToUser. Итог: роли нет, назначения роли нет, письмо есть.
2) scope1 в режиме suppress, уровень изоляции read uncommitted - роль читается, данные записываются, назначение роли создается, письмо отправляется, rollback ничего не отказывает. Итог: роли нет, назначение роли есть, письмо есть.
3) scope1 в режиме suppress, уровень изоляции любой, кроме read uncommitted - роль не читается, запрос падает по таймауту ожидания. Итог: роли нет, назначения нет, письма нет.

Сценарий 3 выглядит самым "чистым" с точки зрения последствий, но является ли он верным?
Возможно, правильнее сначала коммитить scope2, затем вызывать AddRoleToUser? Но что, если логика внутри scope2 зависит от результата работы AddRoleToUser?
Один из напрашивающихся выводов - Role тоже должен создаваться внутри методов api. Да, в идеале должен, но сейчас ситуация не та.
  • Вопрос задан
  • 98 просмотров
Пригласить эксперта
Ответы на вопрос 1
NYMEZIDE
@NYMEZIDE
резюме - ivanfilatov.ru
гуглите паттерн Unit of Work
или вот для начала - Паттерн Unit of Work
UoW есть в EntityFramework, можете переиспользовать его.

если у вас микросервисная архитекрура, то гуглите паттерн Saga
ну или вот почитайте - Паттерн: Сага

а вообще, письмо об измении роли должен рассылать отдельный микросервис, а не код, который пишет в БД.
на основе события, значит в идеале надо поднимать шину (RabbitMQ) и связывать их асинхронно.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы