From d20c9514f2480d9589242f2a43b9c2462a8ad662 Mon Sep 17 00:00:00 2001 From: Matvienko Oleg Date: Fri, 14 Dec 2018 20:45:20 +0500 Subject: [PATCH 1/3] TransactionalDataService --- text/0000-transactional-data-service.md | 69 +++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 text/0000-transactional-data-service.md diff --git a/text/0000-transactional-data-service.md b/text/0000-transactional-data-service.md new file mode 100644 index 0000000..06acd85 --- /dev/null +++ b/text/0000-transactional-data-service.md @@ -0,0 +1,69 @@ +- Дата создания: 2018-12-14 +- RFC PR: (оставьте изначально пустым) +- RFC Issue: (оставьте пустым, если для RFC предварительно не создавалось обсуждение (issue) в RFC-репозитории) +- Flexberry Issue: (оставьте пустым, если для соответствующей проблемы предварительно не создавалась задача (issue) в одном из репозиториев платформы) + +# TransactionalDataService + +## Краткое описание + +> Предлагается реализовать в Flexberry ORM возможность выполнять бизнес-операции в рамках одной транзакции, чтобы все вычитки и обновления объектов через сервис данных внутри бизнес-операции проходили в пределах одной транзакции. Для этого предлагается реализовать декорирующий TransactionalDataService, реализующий интерфейс IDataService и содержащий внутри одну из стандартных реализаций SQLDataService-а. TransactionalDataService должен для всех методов, работающих с БД, некоторым образом определять, создана ли прикладным программистом транзакция, в рамках которой необходимо выполнить это обращение к БД, и, если такая транзакция есть, вызывать подходящие методы внутреннего SQLDataService-а, выполняющиеся в транзакции (...ByExtConn(..., connection, transaction)) + +## Обоснование + +> Сейчас бизнес-логика на наших проектах отрабатывает таким образом, что при вызове `dataService.UpdateObjects(objectsArray)` для каждого обновляемого объекта внутри ORM создается его бизнес-сервер, у которого вызывается метод `OnUpdateИмяОбъекта(UpdatedObject)`. Внутри этого метода может осуществляться множество проверок, в ходе которых происходят вычитки из БД с помощью методов `LoadObjects(lcs)`, а также могут изменяться другие объекты, которые пополнят список отправляемых в БД объектов. Прикладной разработчик имеет возможность отправить объекты на обновление, явно указав подключение к БД и транзакцию, с помощью метода `UpdateObjectsByExtConn`. Этот метод можно вызывать несколько раз по ходу бизнес-операции, передавая одно и то же подключение и транзакцию. В этом случае изменения в БД будут видны только в рамках заданной транзакции до момента вызова `transaction.Commit()` в прикладном коде. Однако все вычитки в методах проверок будут выполняться вне этой транзакции, что, на мой взгляд, противоречит намерению прикладного разработчика, и может привести к некорректным результатам проверок. + +### Пример +Представим себе следующую модель: +> Есть справочник с полями Наименование (строка), Актуальность (логический), НовоеНаименование (ссылка на самого себя). При снятии Актуальности в бизнес-сервере проверяется, что нет других актуальных справочников, ссылающихся на деактуализируемую запись. При этом вызывается метод `DataService.LoadObjects(lcs)`, вычитывающий из БД связанные актуальные справочники. + +> Нужно реализовать бизнес-операцию "Переименование", которая включает в себя: +1. Создание новой записи с новым значением Наименования +2. Перевешивание ссылок с других актуальных справочников со старой записи на новую +3. Простановка старой записи флага Актуальность = false и ссылки НовоеНаименование на созданную новую запись + +> В такой постановке реализовать операцию, используя один вызов `UpdateObjects()` невозможно, поскольку некорректно сработает проверка на наличие ссылок на удаляемую запись. Необходимо использовать `UpdateObjectsByExtConn`, самостоятельно управляя моментом фиксации транзакции. Однако и в этом случае проверка все равно сработает некорректно, потому что `LoadObjects` в прикладном коде проверки не использует transaction и connection и нет готового способа их ему передать. + +## Детальное проектирование + +> Предлагается внести следующие доработки в ORM: +1. Создать интерфейс `ITransactionManager` со свойствами `Connection`, `Transaction` и методом `IEnumerable ExecuteInTransaction(Func> operation)` +2. Создать реализацию этого интерфейса TransactionManager + 1. `Connection`, `Transaction` с приватными setter-ами + 2. `ExecuteInTransaction` проверяет, если `Transaction` и `Connection` `== null`, оборачивает вызов `operation` в создание коннекта и транзакции с try-catch-finally блоком с коммитом и роллбеком транзакции соответственно в try и catch и занулением `Transaction` и `Connection` в finally. Если `Transaction` и `Connection` не нуллы, просто возвращать результат `operation` (это означает, что где-то раньше по стеку вызовов транзакция уже была создана). + 3. Добавить конструктор, принимающий `IDataService`. Конструктор должен проверять, что переданный объект является наследником `SQLDataService`, чтобы мочь создавать транзакции. + 4. Для облегчения тестирования корректности работы и потенциальных усовершенствований в будущем необходимо добавить событие `BeforeCommitTransaction(IEnumerable operationResult)`. Вызывать это событие в `ExecuteInTransaction` перед вызовом `transaction.Commit()`. +3. Создать `sealed class TransactionalDataService:IDataService` + 1. Добавить ему конструктор, принимающий `IDataService` и `ITransactionManager`. В конструкторе проверять, что переданный `IDataService` является наследником `SQLDataService`, `ITransactionManager` не нулл. + 2. В методах сохранения и загрузки объектов проверять значения `_transactionManager.Connection`, `Transaction` и, если они не нуллы, вызывать методы `...ByExtConn(...)` внутреннего `_dataService`, иначе - простые методы внутреннего `_dataService`. +4. Реализовать интеграционные тесты метода `ExecuteInTransaction` в сочетании с `TransactionalDataService`, проверяющие, что вычитки и обновления внутри метода действительно выполняются в рамках транзакции, а явная вычитка из БД вне транзакции не видит результатов обновлений внутри метода до фиксации транзакции. + +> В результате, если прикладной разработчик хочет иметь возможность корректно обрабатывать бизнес-операции в рамках транзакции, он должен сделать следующее: +1. В Unity-конфиге добавить + 1. именованную регистрацию `IDataService` - "decorableDataService". Зарегистрировать в ней какого-либо наследника `SQLDataService`. + 2. регистрацию `ITransactionManager`, заинжектив ему в конструктор + 3. регистрацию `TransactionalDataService` в качестве `IDataService`, заинжектив ему в конструктор "decorableDataService" +2. В прикладном коде перед вызовом метода, реализующего бизнес-операцию, получить реализацию `ITransactionManager` из Unity-контейнера +3. Обернуть вызов метода, реализующего бизнес-операцию, в метод `ITransactionManager.ExecuteInTransaction` + +> В результате будет гарантироваться, что вся работа с базой внутри данного метода будет осуществляться в рамках одной транзакции + +## Документирование и обучение + +> Необходимо будет дополнить раздел документации к ORM, описывающий сервисы данных, добавив туда описание `TransactionalDataService`. В статье про него необходимо также описать `ITransactionManager`, `TransactionManager`, метод `ExecuteInTransaction`, а также необходимые изменения конфигурации Unity. + +## Недостатки + +> Не до конца ясны потенциальные сложности новой реализации `IDataService`. В частности, нет методов `LoadObjectsCountByExtConn`, `LoadStringed...`. Необходимо решить что с ними делать: какие-то реализовать, а какие-то возможно оставить нетронутыми, поскольку они обычно не вызываются в прикладном коде. + +> Также не ясно как правильно построить работу с кешами внутри датасервиса. Возможно возникнут какие-то необычные баги. + +## Альтернативы + +> Озвучивалось предложение не реализовывать отдельный `TransactionManager`, а реализовать метод `ExecuteInTransaction` внутри `TransactionalDataService`. На мой взгляд это решение нарушает принцип единой ответственности: сервис данных должен общаться с БД и осуществлять объектно-реляционный маппинг, а управление транзакциями - отдельная функциональность. Кроме того, разработчик все равно будет вынужден в прикладном коде каким-то образом резолвить объект, содержащий метод `ExecuteInTransaction`. Этот метод на фоне методов `LoadObjects` и `UpdateObjects` находится на ином уровне абстракции, поэтому считаю неправильным смешивать их в одном классе. + +> Озвучивалось предложение реализовать `TransacionManager:IDisposable`, чтобы коннект и транзакция создавались в конструкторе, а удалялись в `Dispose`. Однако не ясно как затем инжектить его в сервис данных. Также не ясно как должны обрабатываться ошибки и в какой момент должна проиходить фиксация или откат транзакции. Кроме того, это создает опасность утечек ресурсов при неаккуратном использовании. Описанный выше способ таких возможностей прикладному разработчику не оставляет. + +## Нерешенные вопросы + +> Что делать с методами `IDataService`, для которых в `SQLDataService` нет транзакционных реализаций? From 0bd72d2c399b0fc753082ab958b7adffb8da1566 Mon Sep 17 00:00:00 2001 From: Matvienko Oleg Date: Fri, 14 Dec 2018 20:51:54 +0500 Subject: [PATCH 2/3] Fix formatting --- text/0000-transactional-data-service.md | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/text/0000-transactional-data-service.md b/text/0000-transactional-data-service.md index 06acd85..abd1969 100644 --- a/text/0000-transactional-data-service.md +++ b/text/0000-transactional-data-service.md @@ -7,26 +7,26 @@ ## Краткое описание -> Предлагается реализовать в Flexberry ORM возможность выполнять бизнес-операции в рамках одной транзакции, чтобы все вычитки и обновления объектов через сервис данных внутри бизнес-операции проходили в пределах одной транзакции. Для этого предлагается реализовать декорирующий TransactionalDataService, реализующий интерфейс IDataService и содержащий внутри одну из стандартных реализаций SQLDataService-а. TransactionalDataService должен для всех методов, работающих с БД, некоторым образом определять, создана ли прикладным программистом транзакция, в рамках которой необходимо выполнить это обращение к БД, и, если такая транзакция есть, вызывать подходящие методы внутреннего SQLDataService-а, выполняющиеся в транзакции (...ByExtConn(..., connection, transaction)) +Предлагается реализовать в Flexberry ORM возможность выполнять бизнес-операции в рамках одной транзакции, чтобы все вычитки и обновления объектов через сервис данных внутри бизнес-операции проходили в пределах одной транзакции. Для этого предлагается реализовать декорирующий TransactionalDataService, реализующий интерфейс IDataService и содержащий внутри одну из стандартных реализаций SQLDataService-а. TransactionalDataService должен для всех методов, работающих с БД, некоторым образом определять, создана ли прикладным программистом транзакция, в рамках которой необходимо выполнить это обращение к БД, и, если такая транзакция есть, вызывать подходящие методы внутреннего SQLDataService-а, выполняющиеся в транзакции (...ByExtConn(..., connection, transaction)) ## Обоснование -> Сейчас бизнес-логика на наших проектах отрабатывает таким образом, что при вызове `dataService.UpdateObjects(objectsArray)` для каждого обновляемого объекта внутри ORM создается его бизнес-сервер, у которого вызывается метод `OnUpdateИмяОбъекта(UpdatedObject)`. Внутри этого метода может осуществляться множество проверок, в ходе которых происходят вычитки из БД с помощью методов `LoadObjects(lcs)`, а также могут изменяться другие объекты, которые пополнят список отправляемых в БД объектов. Прикладной разработчик имеет возможность отправить объекты на обновление, явно указав подключение к БД и транзакцию, с помощью метода `UpdateObjectsByExtConn`. Этот метод можно вызывать несколько раз по ходу бизнес-операции, передавая одно и то же подключение и транзакцию. В этом случае изменения в БД будут видны только в рамках заданной транзакции до момента вызова `transaction.Commit()` в прикладном коде. Однако все вычитки в методах проверок будут выполняться вне этой транзакции, что, на мой взгляд, противоречит намерению прикладного разработчика, и может привести к некорректным результатам проверок. +Сейчас бизнес-логика на наших проектах отрабатывает таким образом, что при вызове `dataService.UpdateObjects(objectsArray)` для каждого обновляемого объекта внутри ORM создается его бизнес-сервер, у которого вызывается метод `OnUpdateИмяОбъекта(UpdatedObject)`. Внутри этого метода может осуществляться множество проверок, в ходе которых происходят вычитки из БД с помощью методов `LoadObjects(lcs)`, а также могут изменяться другие объекты, которые пополнят список отправляемых в БД объектов. Прикладной разработчик имеет возможность отправить объекты на обновление, явно указав подключение к БД и транзакцию, с помощью метода `UpdateObjectsByExtConn`. Этот метод можно вызывать несколько раз по ходу бизнес-операции, передавая одно и то же подключение и транзакцию. В этом случае изменения в БД будут видны только в рамках заданной транзакции до момента вызова `transaction.Commit()` в прикладном коде. Однако все вычитки в методах проверок будут выполняться вне этой транзакции, что, на мой взгляд, противоречит намерению прикладного разработчика, и может привести к некорректным результатам проверок. ### Пример Представим себе следующую модель: -> Есть справочник с полями Наименование (строка), Актуальность (логический), НовоеНаименование (ссылка на самого себя). При снятии Актуальности в бизнес-сервере проверяется, что нет других актуальных справочников, ссылающихся на деактуализируемую запись. При этом вызывается метод `DataService.LoadObjects(lcs)`, вычитывающий из БД связанные актуальные справочники. +Есть справочник с полями Наименование (строка), Актуальность (логический), НовоеНаименование (ссылка на самого себя). При снятии Актуальности в бизнес-сервере проверяется, что нет других актуальных справочников, ссылающихся на деактуализируемую запись. При этом вызывается метод `DataService.LoadObjects(lcs)`, вычитывающий из БД связанные актуальные справочники. -> Нужно реализовать бизнес-операцию "Переименование", которая включает в себя: +Нужно реализовать бизнес-операцию "Переименование", которая включает в себя: 1. Создание новой записи с новым значением Наименования 2. Перевешивание ссылок с других актуальных справочников со старой записи на новую 3. Простановка старой записи флага Актуальность = false и ссылки НовоеНаименование на созданную новую запись -> В такой постановке реализовать операцию, используя один вызов `UpdateObjects()` невозможно, поскольку некорректно сработает проверка на наличие ссылок на удаляемую запись. Необходимо использовать `UpdateObjectsByExtConn`, самостоятельно управляя моментом фиксации транзакции. Однако и в этом случае проверка все равно сработает некорректно, потому что `LoadObjects` в прикладном коде проверки не использует transaction и connection и нет готового способа их ему передать. +В такой постановке реализовать операцию, используя один вызов `UpdateObjects()` невозможно, поскольку некорректно сработает проверка на наличие ссылок на удаляемую запись. Необходимо использовать `UpdateObjectsByExtConn`, самостоятельно управляя моментом фиксации транзакции. Однако и в этом случае проверка все равно сработает некорректно, потому что `LoadObjects` в прикладном коде проверки не использует transaction и connection и нет готового способа их ему передать. ## Детальное проектирование -> Предлагается внести следующие доработки в ORM: +Предлагается внести следующие доработки в ORM: 1. Создать интерфейс `ITransactionManager` со свойствами `Connection`, `Transaction` и методом `IEnumerable ExecuteInTransaction(Func> operation)` 2. Создать реализацию этого интерфейса TransactionManager 1. `Connection`, `Transaction` с приватными setter-ами @@ -38,7 +38,7 @@ 2. В методах сохранения и загрузки объектов проверять значения `_transactionManager.Connection`, `Transaction` и, если они не нуллы, вызывать методы `...ByExtConn(...)` внутреннего `_dataService`, иначе - простые методы внутреннего `_dataService`. 4. Реализовать интеграционные тесты метода `ExecuteInTransaction` в сочетании с `TransactionalDataService`, проверяющие, что вычитки и обновления внутри метода действительно выполняются в рамках транзакции, а явная вычитка из БД вне транзакции не видит результатов обновлений внутри метода до фиксации транзакции. -> В результате, если прикладной разработчик хочет иметь возможность корректно обрабатывать бизнес-операции в рамках транзакции, он должен сделать следующее: +В результате, если прикладной разработчик хочет иметь возможность корректно обрабатывать бизнес-операции в рамках транзакции, он должен сделать следующее: 1. В Unity-конфиге добавить 1. именованную регистрацию `IDataService` - "decorableDataService". Зарегистрировать в ней какого-либо наследника `SQLDataService`. 2. регистрацию `ITransactionManager`, заинжектив ему в конструктор @@ -46,24 +46,24 @@ 2. В прикладном коде перед вызовом метода, реализующего бизнес-операцию, получить реализацию `ITransactionManager` из Unity-контейнера 3. Обернуть вызов метода, реализующего бизнес-операцию, в метод `ITransactionManager.ExecuteInTransaction` -> В результате будет гарантироваться, что вся работа с базой внутри данного метода будет осуществляться в рамках одной транзакции +В результате будет гарантироваться, что вся работа с базой внутри данного метода будет осуществляться в рамках одной транзакции ## Документирование и обучение -> Необходимо будет дополнить раздел документации к ORM, описывающий сервисы данных, добавив туда описание `TransactionalDataService`. В статье про него необходимо также описать `ITransactionManager`, `TransactionManager`, метод `ExecuteInTransaction`, а также необходимые изменения конфигурации Unity. +Необходимо будет дополнить раздел документации к ORM, описывающий сервисы данных, добавив туда описание `TransactionalDataService`. В статье про него необходимо также описать `ITransactionManager`, `TransactionManager`, метод `ExecuteInTransaction`, а также необходимые изменения конфигурации Unity. ## Недостатки -> Не до конца ясны потенциальные сложности новой реализации `IDataService`. В частности, нет методов `LoadObjectsCountByExtConn`, `LoadStringed...`. Необходимо решить что с ними делать: какие-то реализовать, а какие-то возможно оставить нетронутыми, поскольку они обычно не вызываются в прикладном коде. +Не до конца ясны потенциальные сложности новой реализации `IDataService`. В частности, нет методов `LoadObjectsCountByExtConn`, `LoadStringed...`. Необходимо решить что с ними делать: какие-то реализовать, а какие-то возможно оставить нетронутыми, поскольку они обычно не вызываются в прикладном коде. -> Также не ясно как правильно построить работу с кешами внутри датасервиса. Возможно возникнут какие-то необычные баги. +Также не ясно как правильно построить работу с кешами внутри датасервиса. Возможно возникнут какие-то необычные баги. ## Альтернативы -> Озвучивалось предложение не реализовывать отдельный `TransactionManager`, а реализовать метод `ExecuteInTransaction` внутри `TransactionalDataService`. На мой взгляд это решение нарушает принцип единой ответственности: сервис данных должен общаться с БД и осуществлять объектно-реляционный маппинг, а управление транзакциями - отдельная функциональность. Кроме того, разработчик все равно будет вынужден в прикладном коде каким-то образом резолвить объект, содержащий метод `ExecuteInTransaction`. Этот метод на фоне методов `LoadObjects` и `UpdateObjects` находится на ином уровне абстракции, поэтому считаю неправильным смешивать их в одном классе. +Озвучивалось предложение не реализовывать отдельный `TransactionManager`, а реализовать метод `ExecuteInTransaction` внутри `TransactionalDataService`. На мой взгляд это решение нарушает принцип единой ответственности: сервис данных должен общаться с БД и осуществлять объектно-реляционный маппинг, а управление транзакциями - отдельная функциональность. Кроме того, разработчик все равно будет вынужден в прикладном коде каким-то образом резолвить объект, содержащий метод `ExecuteInTransaction`. Этот метод на фоне методов `LoadObjects` и `UpdateObjects` находится на ином уровне абстракции, поэтому считаю неправильным смешивать их в одном классе. -> Озвучивалось предложение реализовать `TransacionManager:IDisposable`, чтобы коннект и транзакция создавались в конструкторе, а удалялись в `Dispose`. Однако не ясно как затем инжектить его в сервис данных. Также не ясно как должны обрабатываться ошибки и в какой момент должна проиходить фиксация или откат транзакции. Кроме того, это создает опасность утечек ресурсов при неаккуратном использовании. Описанный выше способ таких возможностей прикладному разработчику не оставляет. +Озвучивалось предложение реализовать `TransacionManager:IDisposable`, чтобы коннект и транзакция создавались в конструкторе, а удалялись в `Dispose`. Однако не ясно как затем инжектить его в сервис данных. Также не ясно как должны обрабатываться ошибки и в какой момент должна проиходить фиксация или откат транзакции. Кроме того, это создает опасность утечек ресурсов при неаккуратном использовании. Описанный выше способ таких возможностей прикладному разработчику не оставляет. ## Нерешенные вопросы -> Что делать с методами `IDataService`, для которых в `SQLDataService` нет транзакционных реализаций? +Что делать с методами `IDataService`, для которых в `SQLDataService` нет транзакционных реализаций? From 8e71957a501a725851c04f652c284d825e315bfd Mon Sep 17 00:00:00 2001 From: Matvienko Oleg Date: Fri, 14 Dec 2018 21:14:21 +0500 Subject: [PATCH 3/3] Fix missprints --- text/0000-transactional-data-service.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/text/0000-transactional-data-service.md b/text/0000-transactional-data-service.md index abd1969..abc0e7f 100644 --- a/text/0000-transactional-data-service.md +++ b/text/0000-transactional-data-service.md @@ -28,7 +28,7 @@ Предлагается внести следующие доработки в ORM: 1. Создать интерфейс `ITransactionManager` со свойствами `Connection`, `Transaction` и методом `IEnumerable ExecuteInTransaction(Func> operation)` -2. Создать реализацию этого интерфейса TransactionManager +2. Создать реализацию этого интерфейса `TransactionManager` 1. `Connection`, `Transaction` с приватными setter-ами 2. `ExecuteInTransaction` проверяет, если `Transaction` и `Connection` `== null`, оборачивает вызов `operation` в создание коннекта и транзакции с try-catch-finally блоком с коммитом и роллбеком транзакции соответственно в try и catch и занулением `Transaction` и `Connection` в finally. Если `Transaction` и `Connection` не нуллы, просто возвращать результат `operation` (это означает, что где-то раньше по стеку вызовов транзакция уже была создана). 3. Добавить конструктор, принимающий `IDataService`. Конструктор должен проверять, что переданный объект является наследником `SQLDataService`, чтобы мочь создавать транзакции. @@ -36,12 +36,12 @@ 3. Создать `sealed class TransactionalDataService:IDataService` 1. Добавить ему конструктор, принимающий `IDataService` и `ITransactionManager`. В конструкторе проверять, что переданный `IDataService` является наследником `SQLDataService`, `ITransactionManager` не нулл. 2. В методах сохранения и загрузки объектов проверять значения `_transactionManager.Connection`, `Transaction` и, если они не нуллы, вызывать методы `...ByExtConn(...)` внутреннего `_dataService`, иначе - простые методы внутреннего `_dataService`. -4. Реализовать интеграционные тесты метода `ExecuteInTransaction` в сочетании с `TransactionalDataService`, проверяющие, что вычитки и обновления внутри метода действительно выполняются в рамках транзакции, а явная вычитка из БД вне транзакции не видит результатов обновлений внутри метода до фиксации транзакции. +4. Реализовать интеграционные тесты метода `ExecuteInTransaction` в сочетании с `TransactionalDataService`, проверяющие, что вычитки и обновления внутри метода действительно выполняются в рамках транзакции, а явная вычитка из БД вне транзакции не видит результатов обновлений внутри метода до момента фиксации транзакции. В результате, если прикладной разработчик хочет иметь возможность корректно обрабатывать бизнес-операции в рамках транзакции, он должен сделать следующее: 1. В Unity-конфиге добавить 1. именованную регистрацию `IDataService` - "decorableDataService". Зарегистрировать в ней какого-либо наследника `SQLDataService`. - 2. регистрацию `ITransactionManager`, заинжектив ему в конструктор + 2. регистрацию `ITransactionManager`, заинжектив ему в конструктор "decorableDataService" 3. регистрацию `TransactionalDataService` в качестве `IDataService`, заинжектив ему в конструктор "decorableDataService" 2. В прикладном коде перед вызовом метода, реализующего бизнес-операцию, получить реализацию `ITransactionManager` из Unity-контейнера 3. Обернуть вызов метода, реализующего бизнес-операцию, в метод `ITransactionManager.ExecuteInTransaction` @@ -62,7 +62,7 @@ Озвучивалось предложение не реализовывать отдельный `TransactionManager`, а реализовать метод `ExecuteInTransaction` внутри `TransactionalDataService`. На мой взгляд это решение нарушает принцип единой ответственности: сервис данных должен общаться с БД и осуществлять объектно-реляционный маппинг, а управление транзакциями - отдельная функциональность. Кроме того, разработчик все равно будет вынужден в прикладном коде каким-то образом резолвить объект, содержащий метод `ExecuteInTransaction`. Этот метод на фоне методов `LoadObjects` и `UpdateObjects` находится на ином уровне абстракции, поэтому считаю неправильным смешивать их в одном классе. -Озвучивалось предложение реализовать `TransacionManager:IDisposable`, чтобы коннект и транзакция создавались в конструкторе, а удалялись в `Dispose`. Однако не ясно как затем инжектить его в сервис данных. Также не ясно как должны обрабатываться ошибки и в какой момент должна проиходить фиксация или откат транзакции. Кроме того, это создает опасность утечек ресурсов при неаккуратном использовании. Описанный выше способ таких возможностей прикладному разработчику не оставляет. +Озвучивалось предложение реализовать `TransacionManager:IDisposable`, чтобы коннект и транзакция создавались в конструкторе, а удалялись в `Dispose`. Однако не ясно как затем инжектить его в сервис данных. Также не ясно как должны обрабатываться ошибки и в какой момент должна проиcходить фиксация или откат транзакции. Кроме того, это создает опасность утечек ресурсов при неаккуратном использовании. Описанный выше способ таких возможностей прикладному разработчику не оставляет. ## Нерешенные вопросы