Pull to refresh

Comments 22

Проблема Unit of Work в том, что он не может быть реализован для абстрактных репозиториев. Он должен быть с ними тесно связан. В реальности всё чаще всего сводится к тому, что он просто скидывает все свои обязанности на реляционную БД.

Зачем мы вообще создаем абстракции/интерфейсы? Я делаю это ради двух целей:

  1. Тестируемость в изоляции, т.к. можно легко сделать мок/фейк/стаб

  2. Потенциальная замена реализации, если мне, например, потребуется хранить данные в другой БД

Сами по себе абстрактные репозитории - классная вещь. Хотим - храним в Postgres, а хотим - в каком-нибудь key-value хранилище. Но как только мы добавляем UoW - мы сразу привязываемся к реляционной БД с поддержкой транзакций. Смысл в абстрактности репозиториев теряется.

Я предпочитаю обходиться без UoW. Если нужна транзакционность, то можно сделать интерфейс с методом, который будет принимать параметры, описывающие совокупность тех изменений, которые нужно транзакционно выполнить. Такой интерфейс

  1. лучше даст понять тем кто его реализует, что нужна транзакционность

  2. позволит проще её реализовать

  3. потенциально, сделает использующий код чище (менее императивным и более функциональным)

Спасибо за вашу обратную связь.

Согласен, я не обратил внимание, что оставил там абстракцию, такого высокого уровня.

Считаю что может быть абстрактная UoW, но отыгрывать через композицию в конкретных реализациях UoW или фабриках которые собирают это UoW.

Иначе просто наш бизнес код будет зависеть от деталей реализации.

Не совсем понял о каком интерфейсе вы говорите.

Так же не утверждаю, что UoW единственный/лучший вариант. Всё зависит от ситуации.

И статья имеет ознакомительный характер.

Уверен, что кто-то найдёт ему достойное применение )

Ещё раз спасибо!

Сложилось мнение, что UoW это типа команды исполнителей, которая состоит из представителей нескольких специальностей. UseCase выступает у них в роли менеджера-оркестратора. Смысл UoW в том, чтобы менеджер для решения своей задачи работал только со своей командой - заранее ограниченным набором исполнителей, - а не со всей организацией сразу.

Репозиторий отвечает за сохранение агрегата, обеспечивая в том числе и атомарность операций при сохранении всех entities, входящих в этот агрегат. Т.е. транзакции находятся внутри операции, внутри репозитория.

Делать по отдельному "репозиторию" на каждый тип entity (в стиле Table Module) и пытаться оркестровать транзакции снаружи это антипаттерн, поскольку противоречит изначальной идее.

Если мы все задизайнили правильно, но UseCase по прежнему имеет дело с несколькими агрегатами, тогда это Workflow. Скорее всего здесь будет обеспечиваться eventually consistency, нежели транзакционность. В каких-то случаях можно срезать углы с помощью внешней транзакции на базу данных. Но прежде лучше сто раз подумать.

Спасибо за обратную связь!

Агрегат из другой архитектуры(ddd), мешать две архитектуры просто потому что, не мой подход.

тут я 10 раз подумаю, прежде чем создавать агрегат. Особенно из этих двух сущностей.

Создание агрегата должно быть обоснованно, и нужно понимать, какую ответственность это налагает.

С агрегатом вы обязаны управлять жизненным циклом обоих сущностей только через агрегат.

У вас не может быть репозитория клиент+менеджер, и отдельный только на менеджеров. Это сразу нарушит dry, так как ваши знания об одинаковых данных расползутся. Так же будет неправильным доступов к данным, так как с сущностями агрегата можно взаимодействовать только через агрегат.

Неудобства начинаются с самого начала, как только вам нужно получить менеджеров по какому то критерию, касающегося менеджеров.

Для этого вам нужно восстановить всех клиентов у кого будут эти менеджеры, и перебирая клиентов искать их.

Так же для сценария банального "получить всех менеджеров", мне нужно восстановить всех клиентов, взять из них всех менеджеров, и следить за уникальностью, потому что менеджеры могут повторяться.

Ну и заканчивая желанием просто перейти на карточку менеджера, нужно восстановить из памяти весь агрегат, прежде чем получить о нём информацию.

Это ещё всё не так страшно. Если ваш агрегат содержит несколько сущностей, или подчинённая сущность имеет множество вхождений, то при восстановлении агрегата у вас будут восстанавливаться тысячи или десятки тысяч объектов, когда вам нужен лишь один.

Не говоря уже о двух разных сущностных, когда их не объединить в агрегат при всём желании.

Если, как вы говорите, повесить транзакционность уровнем выше чем юзкейс, то ваш юзкейс будет неполноценным.

Во первых, сам юзкейс теряет возможность выделять эту самую логическую транзакцию.

Во вторых, юзкейс как единица вашей бизнес логики на самом деле не обеспечивает её корректность, потому что нужен кто то сверху, кто будет следить за атомарностью.

В третьих, там где вы хотите повесить транзакцию, предположу что это контроллер, вы будете фреймворком зависит от выбора бд.

И так будет работать, но нужно понимать, что это нарушение, и идти на него с осознанием этого, а не просто делать как легче.

Подскажите, что мне сделать если один репозиторий mysql, другой postgresql, третий вообще не sql. Кто должен уметь держать три разные транзакции? Контроллер? Сомневаюсь :)

В завершении повторюсь, что эта статья имеет ознакомительный характер. Не я придумал этот паттерн, и не призываю бездумно использовать его.

мешать две архитектуры просто потому что, не мой подход.

Согласен, здесь проблема ещё в том, что терминология заимствуя разными источниками и разобраться потом о чем вообще речь без полного контекста может быть нереально.

вам нужно получить менеджеров по какому то критерию, касающегося менеджеров.

Обычно в таких случаях рекомендуют использовать другие паттерны, например CQRS. Т.е. обновления идут через репозиторий где обеспечивается атомарность операций, а выборки данных для UI - простыми queries.

что мне сделать если один репозиторий mysql, другой postgresql, третий вообще не sql. Кто должен уметь держать три разные транзакции? Контроллер?

Архитектор). Лечить по симптомам это плохая идея, нужно разобраться зачем у вас так сделано.

Насчёт CQRS не совсем понял, у вас агрегата не будет тогда? Если нет, то и без CQRS на отдельных репозиториях можно жить )

Если имеете ввиду, что кроме репозитория, другие механизмы будут уметь восстанавливать сущности из БД, то у вас размазываются знания об одной сущности на несколько механизмов.

По поводу разных БД, можно упростить - "у вас есть две разных БД", это более реальная ситуация.

Ещё раз упомяну, статья о паттерне, не о "серебреной пуле" в любой ситуации 😊

кроме репозитория, другие механизмы будут уметь восстанавливать сущности из БД, то у вас размазываются знания об одной сущности на несколько механизмов

Мы же занимаемся информационным моделированием, а не стараемся сделать копию реального мира? В информационной системе одна и та же физическая сущность может предоставляться разными моделями.

Требования к данным, которые предъявляет UI, часто сильно расходятся с точкой зрения на них со стороны ETL. Поэтому вполне логично, в решении использовать разные принципы и механизмы.

По поводу разных БД, можно упростить - "у вас есть две разных БД", это более реальная ситуация

Многие люди, чтобы переместить коня из точки А в точку Б, могут это сделать верхом. Профессиональные жокеи могут ехать вверхом на двух одновременно. Это немного против физики, и не так удобно, как на одном, за то публика в восторге. Но не стоит пытаться повторить их опыт, когда требуется перегнать два самолёта - здесь нужно использовать другие подходы. ;)

Ситуация с БД чем-то похожа. Совсем не сложно выполнять транзакции последовательно: сначала в одну базу, затем в другую. Но пытаться усидеть на двух открытых транзакциях для разных баз одновременно это тоже против физики. Для таких ситуаций есть неплохой разбор https://vaadin.com/blog/ddd-part-1-strategic-domain-driven-design.

Мне кажется, что это превращается в ситуацию, когда я пытаюсь защитить этот паттерн. Я лишь хотел рассказать, что он существует и может быть полезен некоторым людям.

Если вы в целом считаете что паттерн плохой или его нельзя использовать, это ваше мнение, я с ним спорить не буду :)

Целью же данной статьи является информирование, а не холивар на тему, какой подход лучше. Лучше тот, который нравится вам и решает ваши задачи :)

Агрегат из другой архитектуры(ddd)

DDD это не архитектура, а подход к моделированию систем.

Не буду вас переубеждать 😊

Да и не надо :)

Предметно-ориентированное проектирование (реже проблемно-ориентированное, англ. domain-driven design, DDD) — набор принципов и схем, направленных на создание оптимальных систем объектов. Сводится к созданию программных абстракций, которые называются моделями предметных областей. В эти модели входит бизнес-логика, устанавливающая связь между реальными условиями области применения продукта и кодом.

Идея в том, что UoW знает обо всех репозиториях

"Репозиторий" это отдельный паттерн и UoW никак с ним напрямую не связан. "Под капотом" у UoW может быть все что угодно.

От вашего UoW зависит UseCase. UseCase будет использовать конкретные зависимости из UoW. Если вы не раскрываете, что лежит в UoW, как UseCase(у) взаимодействовать с ним?

В стандартном исполнении это репозитории, так как UoW по канонам используется для баз данных. Если у вас используются другие паттерны для CRUD работы с БД, то расскажите пожалуйста ваш сценарий использования UoW.

Да, чаще всего для БД, но совсем необязательно.

Точное определение по Фаулеру (P of EAA):

UoW maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.

Как видите, ни про репозитории, ни про БД тут ни слова. DataContext из EF или DataSet из ADO.NET - типичные примеры реализации UoW, но никакими репозиториями там и не пахнет.

В моём примере используются именно они, поэтому я и упоминаю, что UoW знает о них.

Если вы используете другие подходы, хорошо. Это не отменяет того что UoW должен знать о них и предоставлять клиентскому коду. Спасибо за обратную связь :)

Это не отменяет того что UoW должен знать о них и предоставлять клиентскому коду.

Вам знакомо понятие "протечка абстракции"? Если нет, то вот это она самая и есть.

Если это возможно, покажите минимальный пример, как вы взаимодействуете с UoW в клиентском коде :)

Ну, вот, возьмем как пример EntityFramework. Сначала клиентский код вызывает его LINQ методы при этом в DataContext загружаются запрошенные объекты-сущности. Затем клиент производит требуемый набор манипуляций с этими объектами (изменения свойств, удаление и добавление новых объектов). После всего этого клиент вызывает метод SaveChanges, который (в одной общей транзакции) передает в БД все сделанные до этого изменения. Само взаимодействие между DataContext и БД при этом от клиента полностью скрыто.

EntityFramework это уже сторонняя вещь, правильно? И если клиентский код вызывает его методы, то у вас изначально бизнес логика завязана со сторонним фреймворком?

А какая разница, что сторонняя. Ну напишу я от нечего делать свой собственный ORM вместо EF - что изменится? UoW это паттерн работы с данными, а не БЛ, которой в нем быть не должно. Место БЛ в сущностях (rich domain model) или сервисах (anemic model), либо (что чаще всего) и там и там. А ответственность UoW это только эти сущности "пакетно" читать/писать куда либо в рамках отдельной транзакции.

Я не буду спорить. Считаю что наши мнения расходятся на этот счёт. Может быть мы просто не понимаем друг друга. Если вам не понравилась статья, это нормально. В любом случае спасибо вам за обратную свзяь! 😊

Не соглашусь что про БД ни слова. В книге, Мартин многократно упоминает про базы данных(говоря о UoW). Сама глава про UoW начинается со слов - "Извлекая данные из базы данных ..."

Sign up to leave a comment.

Articles