1283 / 863 / 257
Регистрация: 08.08.2014
Сообщений: 2,462
|
|
1 | |
Управление транзакциями18.04.2019, 22:10. Показов 3029. Ответов 4
Метки нет (Все метки)
Вопрос больше по архитектуре, чем по конкретной реализации.
Есть какие-то проверенные практики по управлению транзакциями в рамках определённого контекста выполнения (например, в пределах одного API-запроса)? К тому, что ниже - я в курсе, что транзакции могут быть не только поверх конкретной базы под ORM, что они могут покрывать сразу несколько БД, могут включать в себя обращения к другим сервисам или очередям. Тут пока интересует самый простой случай, дабы совсем не запутаться. Например, для стандартного сценария прохождения запроса на API: 1. Контроллер. 2. Сервис БЛ <==> Сервис БЛ <==> Сервис БЛ. 3. Репозиторий-1, Репозиторий-2. 4. ORM. Ключевой пункт [2], где сервисы могут переиспользовать друг-друга в неожиданном порядке (т.е. они с точки зрения архитектуры все на одном уровне) и каждому из них может требоваться или не требоваться транзакция при общении с [3]->[4]. Из этого сходу напрашивается очевидное решение - регистрировать репозиторий/контекст-ORM со временем жизни 'Scoped', где-то до [2] заранее всегда открывать транзакцию при прохождении запроса вперёд и закрывать/откатывать при возврате результата. Например, в Invoke мидлвара, непосредственно перед вызовом метода контроллера.Это удобное решение с точки зрения "один раз сделал и забыл", но, имхо, с точки зрения архитектуры кривоватое, т.к. сервисы теряют контроль над частью того функционала, который относится к их области ответственности (их ведь, да?). Т.е. если этот сервис вдруг будет переиспользован в другом API, то тот API тоже будет вынужден знать об особенностях применения транзакций этими сервисами. Другая мысль - сервис БЛ сам открывает и закрывает транзакции, если ему это необходимо. Однако, в этом случае, если этот сервис вызывает какой-то другой сервис и ему тоже нужна транзакция, то нужно чтобы оба они работали в рамках одной транзакции, и появляется необходимость делать костыль с подсчётом количества открытий/закрытий транзакций и игнорированием лишних открытий и непоследних закрытий. Т.к. если транзакция жива в рамках всего запроса (или цепочки методов сервисов), то повторное её открытие приведёт к ошибке, так же как и преждевременное закрытие.
0
|
18.04.2019, 22:10 | |
Ответы с готовыми решениями:
4
Управление транзакциями Управление транзакциями в С++ Работа с транзакциями Проблемы с распределёнными транзакциями |
12504 / 8688 / 1310
Регистрация: 21.01.2016
Сообщений: 32,636
|
|
20.04.2019, 10:01 | 2 |
Сообщение было отмечено kotelok как решение
Решение
Именно такое решение.
А вот это уже фигня) Используйте класс TransactionScope . Он допускает вложенные транзакции. Если сервису хочется открыть транзакцию, то он может это сделать со спокойной совестью. Ему не нужно знать о том, что транзакцию уже открыли. Провайдер ADO.NET тоже знает о TransactionScope (как и все ORM) и о возможности вложения транзакций.Всё придумано до вас.
1
|
1283 / 863 / 257
Регистрация: 08.08.2014
Сообщений: 2,462
|
|
20.04.2019, 12:59 [ТС] | 3 |
Пока не получилось разобраться, как прозрачно встроить его в проект.
Сейчас сделано на простом костыле - LinqToDB.Data.DataConnection регистрируется со временем жизни Scope (т.е. в рамках одного API-запроса), при создании этого объекта он автоматически открывает подключение к БД и удерживает его (это не я, он сам так себя ведёт).Ну и в реализации DataConnection просто переопределил методы Begin/Commit/Rollback и считаю там количество открытий/закрытий, как описал выше.Правда, это порождает некоторые неоднозначные ситуации и проблемы: 1. Транзакция открывается на уровне объекта ORM, сервис БЛ к этому объекту доступа не имеет, приходится в репозиториях (наследованием от базового репозитория) выставлять наружу свои методы Begin/Commit/Rollback . Кривовато выглядит.2. Первый сервис открыл транзакцию, вложенный сервис тоже (по факту ничего не произошло), потом вложенный сервис откатил транзакцию (по факту ничего не произошло), а первый сервис её закоммитил. В итоге изменения вложенного сервиса тоже закоммитились. С другой стороны, это весьма странная ситуация с точки зрения БЛ, т.е. по идее, если вложенный сервис не смог отработать, он должен кинуть исключение и верхний сервис тоже откатит транзакцию. Добавлено через 34 минуты Как-то оно странно работает: 1. Сервис-1 создаёт TransactionScope (внутри using). Добавляет запись в таблицу. Вызывает метод Сервис-2.2. Сервис-2 создаёт TransactionScope (внутри using). Добавляет запись в таблицу. Делает Complete .3. Сервис-1 получает управление обратно. Далее (для теста) генерирует исключение. Т.е. он не доходит до вызова Complete .4. Результат: обе записи добавлены в таблицу. Добавлено через 9 минут Не подскажете, как правильно использовать TransactionScope в рамках описанной в первом посте задачи и в сочетании с LinqToDb ?Пока что, без TransactionScope , получилось проапгрейдить реализованный выше велосипед до отдельного сервиса с временем жизни Scoped , который имеет методы Begin/Commit/Rollback и знает про все коннекты уровня сессии. Это позволило убрать работу с транзакциями на уровне репозиториев. Теперь, если сервису БЛ нужна транзакция, он обращается к сервису транзакций, а тот уже разруливает вложенность обращений. Дополнительное удобство - т.к. нет необходимости оборачивать запуск транзакции в блок using , методы БЛ выглядят аккуратнее (без лишних отступов).
0
|
12504 / 8688 / 1310
Регистрация: 21.01.2016
Сообщений: 32,636
|
||||||
20.04.2019, 13:27 | 4 | |||||
Вы в этом уверены? Подключение должно открываться только на время выполнения запроса и после его выполнения - закрываться. Добавлено через 1 минуту Пфффффф. Что тут ещё можно сказать...
1
|
1283 / 863 / 257
Регистрация: 08.08.2014
Сообщений: 2,462
|
|||||||||||||||||||||
20.04.2019, 15:56 [ТС] | 5 | ||||||||||||||||||||
----
C проблемой разобрался, "простыня" новых вопросов не содержит, так что её можно не читать. Решение в последнем абзаце. ---- контекст БД
регистрация
Когда какой-то сервис запрашивает MainDb , прилетает вызов в лябмду (где регистрация), создаю объект, попадаю в конструктор MainDb и в этот момент свойство Connection инициализировано и находится в состоянии Open . Хотя, вероятно, оно самоинициализируется и открывается при попытке просмотра его в отладке (наверное, можно как-то через рефлекшн это проверить).Добавлено через 58 секунд Usaga
Я выше написал - если внутри вложенного сервиса создаю ещё один TransactionScope , там его коммичу, а в сервисе уровнем выше падаю с исключением до Complete , то в базу коммитятся все изменения - и из внутреннего сервиса, которой закоммитился, и из вышестоящего, который упал.Добавлено через 7 минут Да, проверил через reflection, подключение и в самом деле 'null' до первого использования. Инициализируется при просмотре через окно отладки. Добавлено через 15 минут И тогда я не понимаю, если LinqToDb каждый раз при выполнении запроса открывает/закрывает соединение, то как TransactionScope убедит его держать одно открытое соединение с активной транзакцией при межсервисных вызовах?Добавлено через 15 минут Не убеждает ). Вот этот вызов добавляет запись в БД, хотя tr.Complete() нигде не вызывается:
Всё, разобрался. В строке подключения к БД требуется явно задать параметр: Enlist=True; . После этого TransactionScope начинает работать корректно. По крайней мере под Виндой. Надо будет ещё пол Дебианом проверить.
0
|
20.04.2019, 15:56 | |
20.04.2019, 15:56 | |
Помогаю со студенческими работами здесь
5
Разобраться с транзакциями MySQL+dbExpress Запуск внешних приложений из 1С:Предприятия и работа с транзакциями Реализовать алгоритм работы планировщика. Управление виртуальной памятью. Управление файловой системой Движение, вращение, управление движением, управление вращением фигуры Искать еще темы с ответами Или воспользуйтесь поиском по форуму: |