Вот и получается, что целых два агрегата у нас есть, действия агрегатов определены, а то, что эти действия бессмысленны друг без друга, за это отвечает внешний по отношению к агрегатам сценарий. И прочий код, работающий с приглашениями и пользователями (а у меня есть пример, где создание пользователя из приглашения не самостоятельный UseCase, а часть другого, более сложного UseCase) должен быть осведомлён не только о моделях агрегатов, но и о сервисах, связывающих эти модели. В этом месте, правда, могут любители доменных событий подойти, чтобы ещё больше всё запутать.
Почему же ваш услужливый оператор работает сразу в двух агрегатах ("создаёт учётную запись" и "отмечает номер учётной записи в своей табличке с приглашениями")? Кто такой этот "оператор" в программном коде?
Ну и в чём тогда выигрыш. Вот мы разложили всё по "сервисам" - переиспользуемую бизнес-логику и "use-case'ы" - конкретные бизнес-сценарий. У нас система развивается и "конкретный бизнес-сценарий" превратился в "переиспользуемую бизнес-логику". Так что границы между этими понятиями, которые мы для себя нарисовали, оказались весьма условны.
Так очевидно же. Как выглядит типичный код с применением DDD?
var request = repository.GetRequest(requestId);
request.Approve(userId);
repository.SaveChanges();
Ну и как вы в данном случае реализуете репозиторий без ORM? ChangeTracker в рукопашку забабахаете?
Ну и сами "доменные модели". Я застал ещё время, когда вся эта доменка представляла собой набор свойств с {get; set}и конструктором по умолчанию. Потому что EF (без Core) ничего сложнее мапить не умел. Сейчас EF многому научился, но уши персистентности из моделей всё равно там и тут торчат. Вот и получается, что проектирование _моделей_, самого сердца DDD, прочно связано с возможностями ORM.
Подпишусь под многим. Правда, тут не в ASP.NET и не в EF дело. И даже не в DDD, а в IT-евангелизме как таковом.
Все озвученные вами проблемы - они и на других платформах и с другими инструментами будут проявляться при внедрении хардкорного тру-DDD подхода. А вот если без хардкора, а просто взять без фанатизма предлагаемый DDD способ _переиспользования_кода_ (через модели), то и с DDD можно жить.
Вообще, я вообще очень люблю книгу Эванса, но больше в soft-части, там где речь идёт о важности дистилляции понятий и моделей. К тактическим паттернам немало вопросов, но в книге для меня они не главные.
Вот у меня есть сценарий - принятие пользователем приглашения о присоединении к системе.
Пользователь проходит по ссылке-приглашению, в результате запись из таблички invitations удаляется, а запись в табличку users добавляется. Кто тут кому root aggregate и как через DDD должны быть организованы события, чтобы всё это дело не рассыпалось? Как это сделать без DDD я и так знаю.
Вот это интересный момент, конечно. Один мой коллега-дотнетчик, когда его отправили учить жизни php-шников, в разговоре с их начальником проронил такую фразу: "ну, я не знаю, есть у вас ORM или нет". От этого по его мнению зависело, можно на их проекте внедрить DDD или нельзя.
И тут корень противоречия. DDD на "философском уровне" про "Tackling Complexity in the Heart of Software" и про полное абстрагирование от способов хранения. А на практике - "если у вас нет ORM, то нет DDD". И ведь действительно очень сложно реализовать стандартные DDD-паттерны проектирования без ORM, если у вас не документоориентированная база данных.
1. Вот был у меня UseCase - создание приглашения о присоединении к системе для пользователя (в табличку запись добавляется, пользователю письмо улетает). Потом появился новый сценарий - принятие запроса на временное присоединение к пространству пользователя специалиста поддержки, в который так же входит отправка приглашения пользователю (в табличку запись добавляется, пользователю письмо улетает). Теперь мой первый UseCase - уже не UseCase, если это часть другого UseCase? Что мне делать с "последовательностью управляющих конструкций"? Продублировать в двух UseCase'ах? Вынести код из первого UseCase в разделяемый сервис/модель? Вызвать один UseCase из другого?
2. Если сервисы делать маленькими, то это уже UseCase'ы?
Мне из статьи больше идея фича-лида понравилась. Один человек должен прорабатывать требования к фиче, нарезать задачи и принимать MR'ы. Когда во всей команде занимается этим один человек (тим-лид), то на него приходится очень большая нагрузка. Когда фичей никто выделенно не занимается, тогда и планирование превращается в лотерею, и "чужие" MR воспринимаются просто как внешний раздражающий фактор. Да и код-ревью задачи, в которую ты не погружён, вырождается в "скобочки не так расставил".
Ну, хэш очевидный воркэраунд, если забыть о коллизиях. Коллизии, кстати, более вероятны, если использовать в качестве ключа блокировки int id из какого-нибудь последовательно растущего sequence. Можно договариваться о диапазонах, конечно (используя первый параметр int, например), но всё это... неудобно.
С pg_advisory_lock для меня основное ограничение в том, что она только bigint принимает (8 байт). Произвольную строку в качестве ключа тут использовать не получится, guid использовать не получится... RedLock поэтому оказывается удобней.
Сергея Теплякова в списке категорически приветствую. Со своим самым долгоиграющим багом я когда-то справился благодаря тому, что вспомнил когда-то написанную им статью.
Плюсанусь. Для Getting started может и пойдёт, но Osherove для меня сейчас книга про то, как не надо писать тесты, а Хориков - про то, как надо. Это у Osherove, кажется, был совет делать все методы класса виртуальными, чтобы их всегда можно было замокать.
А откуда в данном процессе возьмётся агрегат User, если User ещё не создан?
Вот и получается, что целых два агрегата у нас есть, действия агрегатов определены, а то, что эти действия бессмысленны друг без друга, за это отвечает внешний по отношению к агрегатам сценарий. И прочий код, работающий с приглашениями и пользователями (а у меня есть пример, где создание пользователя из приглашения не самостоятельный UseCase, а часть другого, более сложного UseCase) должен быть осведомлён не только о моделях агрегатов, но и о сервисах, связывающих эти модели. В этом месте, правда, могут любители доменных событий подойти, чтобы ещё больше всё запутать.
Так где же этот код, работающий с разными агрегатами, будет находиться? В модели, в обработчике доменного события, в сервисе, в репозитории?
Почему же ваш услужливый оператор работает сразу в двух агрегатах ("создаёт учётную запись" и "отмечает номер учётной записи в своей табличке с приглашениями")? Кто такой этот "оператор" в программном коде?
Ну и в чём тогда выигрыш. Вот мы разложили всё по "сервисам" - переиспользуемую бизнес-логику и "use-case'ы" - конкретные бизнес-сценарий. У нас система развивается и "конкретный бизнес-сценарий" превратился в "переиспользуемую бизнес-логику". Так что границы между этими понятиями, которые мы для себя нарисовали, оказались весьма условны.
Транзакцией в БД все проблемы, конечно, не решишь, а вот если на транзакции в БД забить, то можно поиметь очень много новых проблем на пустом месте.
Ну и как сохранять то, без ChangeTracker? Хочу услышать ответ. Написать свой ChangeTracker вместо ORM?
Так очевидно же. Как выглядит типичный код с применением DDD?
Ну и как вы в данном случае реализуете репозиторий без ORM? ChangeTracker в рукопашку забабахаете?
Ну и сами "доменные модели". Я застал ещё время, когда вся эта доменка представляла собой набор свойств с
{get; set}и конструктором по умолчанию. Потому что EF (без Core) ничего сложнее мапить не умел. Сейчас EF многому научился, но уши персистентности из моделей всё равно там и тут торчат. Вот и получается, что проектирование _моделей_, самого сердца DDD, прочно связано с возможностями ORM.Подпишусь под многим. Правда, тут не в ASP.NET и не в EF дело. И даже не в DDD, а в IT-евангелизме как таковом.
Все озвученные вами проблемы - они и на других платформах и с другими инструментами будут проявляться при внедрении хардкорного тру-DDD подхода. А вот если без хардкора, а просто взять без фанатизма предлагаемый DDD способ _переиспользования_кода_ (через модели), то и с DDD можно жить.
Вообще, я вообще очень люблю книгу Эванса, но больше в soft-части, там где речь идёт о важности дистилляции понятий и моделей. К тактическим паттернам немало вопросов, но в книге для меня они не главные.
Вот у меня есть сценарий - принятие пользователем приглашения о присоединении к системе.
Пользователь проходит по ссылке-приглашению, в результате запись из таблички invitations удаляется, а запись в табличку users добавляется. Кто тут кому root aggregate и как через DDD должны быть организованы события, чтобы всё это дело не рассыпалось? Как это сделать без DDD я и так знаю.
>Мне казалось, что DDD без ORM не живут.
Вот это интересный момент, конечно. Один мой коллега-дотнетчик, когда его отправили учить жизни php-шников, в разговоре с их начальником проронил такую фразу: "ну, я не знаю, есть у вас ORM или нет". От этого по его мнению зависело, можно на их проекте внедрить DDD или нельзя.
И тут корень противоречия. DDD на "философском уровне" про "Tackling Complexity in the Heart of Software" и про полное абстрагирование от способов хранения. А на практике - "если у вас нет ORM, то нет DDD". И ведь действительно очень сложно реализовать стандартные DDD-паттерны проектирования без ORM, если у вас не документоориентированная база данных.
1. Вот был у меня UseCase - создание приглашения о присоединении к системе для пользователя (в табличку запись добавляется, пользователю письмо улетает).
Потом появился новый сценарий - принятие запроса на временное присоединение к пространству пользователя специалиста поддержки, в который так же входит отправка приглашения пользователю (в табличку запись добавляется, пользователю письмо улетает). Теперь мой первый UseCase - уже не UseCase, если это часть другого UseCase? Что мне делать с "последовательностью управляющих конструкций"? Продублировать в двух UseCase'ах? Вынести код из первого UseCase в разделяемый сервис/модель? Вызвать один UseCase из другого?
2. Если сервисы делать маленькими, то это уже UseCase'ы?
Мне из статьи больше идея фича-лида понравилась. Один человек должен прорабатывать требования к фиче, нарезать задачи и принимать MR'ы. Когда во всей команде занимается этим один человек (тим-лид), то на него приходится очень большая нагрузка. Когда фичей никто выделенно не занимается, тогда и планирование превращается в лотерею, и "чужие" MR воспринимаются просто как внешний раздражающий фактор. Да и код-ревью задачи, в которую ты не погружён, вырождается в "скобочки не так расставил".
>По факту ребятам нужно было время, чтобы вникнуть в задачу и понять, как её реализовать, а потом уже разрабатывать.
Но, как обычно, на планировании все видят задачу первый раз, в постановке никто не разбирался, поэтому метают кости играя в "покер"?
Ну, хэш очевидный воркэраунд, если забыть о коллизиях.
Коллизии, кстати, более вероятны, если использовать в качестве ключа блокировки int id из какого-нибудь последовательно растущего sequence. Можно договариваться о диапазонах, конечно (используя первый параметр int, например), но всё это... неудобно.
С pg_advisory_lock для меня основное ограничение в том, что она только bigint принимает (8 байт). Произвольную строку в качестве ключа тут использовать не получится, guid использовать не получится... RedLock поэтому оказывается удобней.
Сергея Теплякова в списке категорически приветствую. Со своим самым долгоиграющим багом я когда-то справился благодаря тому, что вспомнил когда-то написанную им статью.
The danger of TaskCompletionSource
https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/
Я бы тоже для начинающего посоветовал Албахари, а не любимого Рихтера.
Плюсанусь. Для Getting started может и пойдёт, но Osherove для меня сейчас книга про то, как не надо писать тесты, а Хориков - про то, как надо. Это у Osherove, кажется, был совет делать все методы класса виртуальными, чтобы их всегда можно было замокать.
Без тестов - завернуть, а с хорошими тестами можно попробовать. В библиотечном коде такие вещи более оправданы.