Comments 46
Это как если бы директор кладбища хранил бы руки в одном месте, ноги - в другом. Потому что ему так удобно для логистики, эффективного землепользования и т.д. А вот родственникам совсем не удобно.
Добавлю ещё один тейк который забыл указать - идеология существования DDD - это борьба со сложностью предметной области.
Структурирование по типам - это технический слой абстракции который именно добавляет сложности в модель.
Создание папок Entity/ValueObject/Repository/Service/Event не добавляет информации о бизнесе и только заставляет разработчиков думать о паттернах, а не о домене. Это так же увеличивает количество точек входа и “прыжков” по коду
В терминах DDD: это именно рост когнитивной нагрузки, а не её уменьшение. Что противоречит самому подходу на мой взгляд.
Неужели хранение в одной папке событий TemplateChanged.php, TemplateArchivated.php и репозитория TemplateRepositoryInterface.php - это и есть результат многолетнего осмысления ддд?
Да, все эти классы относятся к агрегату и являются его саппортом/результатом. Поэтому они должны лежать вместе. Согласитесь странно если кошелек лежит в одном кармане а деньги в другом только потому что это два разных типа предмета.
Когда начнёте смотреть на папки как на модули/неймспейсы/пакеты то становится более понятно.
Когда начнёте смотреть на папки как на модули/неймспейсы/пакеты то становится более понятно
Каждый день смотрю на на модули/неймспейсы. Но хранить вместе события и репозитории никогда не буду. Но это конечно дело вкуса.
А не странно будет если в этом вашем кошельке у вас пачка денег, набор кредитных карт, водительское удостоверение и фотография жены с ребенком все будет лежать в одном кармане, а не каждый в своей секции?
Устал уже отвечать на комментарии про "все в одну кучу". Это либо троли либо незнакомые с DDD люди. Пускай это будет последним ответом на это:
Здесь речь не о том что бы положить все в одну кучу, а о том что бы положить все по предметной области. На пример кошелек можно разделить на отделы:
Отдел для машины где лежит ВУ+полис и ТД
Отдел для спорта где лежат скидки и карты лояльности по спорту и ТД
Отдел для чаевых где лежат деньги которые тратятся только на чаевые.
Примеров можно привести кучу - вопрос фантазий, ниже в комнатах уже были хорошие примеры про кладбище и пирожки D
Здесь нет никакого "сложить все в кучу".
ИМХО - люди которые читают эту статью и видят только "сложить все в кучу" не имеют никакого представления о DDD. Ведь сама суть этого подхода - работа с предметной областью.
А когда вы сеньор помидор который всю жизнь кладет код по типам потому что увидел это в скелетоне какого нибудь фраемворка 20 лет назад и уже просто не может абстрагироваться от паттернов и думать в рамках единого языка и предметной области - у меня плохие новости: ни эта статья, ни ддд не для вас. Вы будете и дальше держать в голове бизнес а в коде паттерны и тратить колоссальное количество времени на паминг того что от вас хотят заказчики на то какими чудесными патернами вы это реализовали. А ещё большее время будут тратить коллеги работающие с вашим кодом что бы понять из как же паттернов предыдущий разработчик реализовал эту бизнес фитчу(по каким же папкам она разработана, что бы я посмотрел на каждый файлик и собрал в голове все воедино)
Добавлю ещё один тейк который забыл указать - идеология существования DDD - это борьба со сложностью предметной области.
Структурирование по типам - это технический слой абстракции который именно добавляет сложности в модель.
Создание папок Entity/ValueObject/Repository/Service/Event не добавляет информации о бизнесе и только заставляет разработчиков думать о паттернах, а не о домене. Это так же увеличивает количество точек входа и “прыжков” по коду
В терминах DDD: это именно рост когнитивной нагрузки, а не её уменьшение. Что противоречит самому подходу на мой взгляд.
Чтобы в доме был порядок, "вещи должно быть удобно класть", это важнее, чем правило "вещи должно быть удобно брать".
При разработке поддерживаемой архитектуры, которую пишут большем чем 1 человек и дольше чем одну неделю, удобно пользоваться тем же правилами.
Еще вопрос в однозначности интерпретации.
Порядок + однозначность интерпретации > выразительность модели через файловую структуру.
Что может быть более однозначно чем сама модель, а не хаотичный набор Патерсон где что бы понять фичу нужно просмотреть все паттерны и найти среди них то что относится к фиче
Модель сама по себе не снимает неоднозначность.
Один и тот же кусок логики можно интерпретировать по-разному.
Структура проекта - это не про паттерны и не про выразительность, а про однозначную навигацию: где искать use-case, где доменные правила, где ошибки и события.
Если для каждой фичи это место угадывается одинаково - интерпретация становится однозначной. Если нет - приходится "восстанавливать модель по коду", а это дорого.
Это как если бы директор кладбища хранил бы руки в одном месте, ноги - в другом. Потому что ему так удобно для логистики, эффективного землепользования и т.д. А вот родственникам совсем не удобно.
Неверная аналогия.
Представьте лучше, что вы продаёте пирожки. Поставщик привёз вам новую партию, но свалил в одну кучу пирожки с мясом, капустой и яйцом. Тут приходит клиент и требует пирожок с капустой. Какие будут ваши действия?
Если это комплексный обед, состоящий из нескольких пирожков разных типов, то да -- их нужно организовывать в такие кучки. Вопрос ведь не в том, чтобы свалить все в одну кучу, а в принципе структуризации -- по контексту или по типу. И если в домене выделяются несколько контекстов, то следует организовывать структуру именно по этому аспекту.
Имею вопрос.
Как в предложенной файловой структуре понять, что Product - основной домен, а Template - поддерживающий?
Куда поместить доменный сервис, который работает сразу с двумя агрегатами?
Зависит от сути сервиса.
Например CalculateRecomendationScore.php может работать и с шаблоном и с продуктом(смотреть остатки продуктов которые входят в шаблон например)
Так же и сервис MoveProductToTemplate.php может изменять продукт и так же работать с шаблоном например для проверки подходит ли этот продукт под нахождение в этом шаблоне.
Каждый случай уникален, особенно в случаях когда бизнес не позволяет сделать нам eventual consistency для обновления двух агрегатов через события.
Не является ли проблемой с точки зрения Единого Языка, что у вас в каталоге AssignProductToTemplate/ используется три разных глагола (Assign, Move, Changed)?
Как будто бы вложенные файлы должны быть AssignProductToTemplate.php и ProductTemplateAssigned.php
PositiveInteger является бизнес инвариантом или валидатором?
И тем и тем, например для VO скидка можно сделать VO и указать там правило что скидка не может быть 0 и меньше 0, теже правила скорее всего будут у цены, цена продукта не может быть 0 и ниже 0. Такие вещи объединяются в shared VO что бы не плодить слишком много однотипных VO ради повторяющихся инвариантов
Спасибо за статью. Вопрос из практики.
Часто сталкиваюсь с ситуацией, когда Domain начинает разрастаться, и граница между доменной логикой и прикладными сервисами становится размытой.
Например: есть доменная сущность Order и правило пересчёта итоговой суммы с учётом скидок, налогов и внешних ограничений (лимиты, акции).
Формально это бизнес-правило, но часть данных и решений приходит из Application / Infrastructure.
В таких случаях вы оставляете логику в Domain (через абстракции/политики) или предпочитаете выносить оркестрацию в Application?
Есть ли у вас практический критерий, по которому вы принимаете это решение?
Когда стоит вопрос "положить в прикладной или доменный слой" - я прибегаю абстрагированию от ИТ. Переношу сценарий на 100500 лет назад где не было компьютеров а люди жили в пещерах. И все те вещи которые есть как и в пещерное время так и сегодня независимо от технологий - 100% кладу в domain.
Но такое тоже не всегда можно провернуть. Если у нас есть несколько разных use case что бы создать продукт, то скорее всего различия между этими use case будут аркестрироваться в прикладном слое.
Например если продукт создаёт главный админ мы его просто создаём.
А если продукт создаёт саппорт-менеджер - нам нужно проверить несколько внешний условий и при их успехе создать продукт - саму логику условий будет в домене а ее использование в прикладном слое.
В моем варианте будет папка Domain/Create product/ и в ней будут сервисы/интерфейсы/политики отвечающие за создание продукта которые будут вызываться в разных use case в разной вариации.
Спасибо, хороший пример с разными ролями и сценариями.
Тогда попробую зафиксировать границу, чтобы проверить, правильно ли я вас понял.
Я для себя обычно формулирую так:
Domain отвечает за то, что допустимо в предметной области (инварианты, политики, правила),
Application - за то, когда, кем и в каком порядке эти правила применяются.
Поэтому для меня тревожный сигнал, если Domain-сервис начинает:
знать о ролях пользователей,
различать use case'ы,
принимать решения о последовательности шагов.
На мой взгляд, в этот момент он уже превращается в 'скрытый use case', просто лежащий не в том слое.
Вопрос: есть ли у вас практические стоп-сигналы, по которым вы понимаете, что доменный сервис пора упрощать или дробить, чтобы он не начал оркестрировать сценарии?
Здесь без конкретики сложно что-то придумать. Мне лично очень помогают атрефакты Event Storming. По ним становится понятнее - это часть домена или часть юзкейса.
Но на практке Event Storming применятся очень редко из-за его дороговизны - все нюансы становятся видно только со временем разработки. Чем больше мы узнаем/реализуем нашу модель - тем больше мы начинаем понимать, что этот сервис стоит вынести в юзкейс. А этот юзкейс вообще не юзкейс, а бизнес фича которая не зависит от входных условий.
Возможно, более базово погрузится в этот вопрос может помочь книга по UML от Ивара Якобсона, а точнее её раздел про варианты использования. По слухам бородатых дядек - это можно назвать первоисточником определения use case.
А что думаете о подходе, когда папки организованы, как в первом случае, но все интерфейсы лежат в корне домена? То есть по набору интерфейсов сразу видно, что делает домен, а реализация уже по паттернам
Данный подход очень удобен и полезен для маленьких проктов/bounded context. Где нет много сущностей, сложной логики, больших связей между сущностями и реакций на действия сущностей. Т.к. основная проблема данного подхода - чем больше проект - тем сложнее в нем ориентироваться. в папке Entity появляются подпапки в подпапках еще подпапки и мы получаем набор классов которые непонятно как живут друг с другом.
Всякие поддерживающие контексты которые не требуют дорогой проработки и постоянного развития и сопровождения - очень выгодно реализовывать таким подходом. Мы быстро накидали код, задеплоили и забыли про него. Он просто выполняет свою функцию и не развивается.
Не знаю хорошо это или плохо но уже произошла какая-то "проф деформация" и уже даже мелкие поддерживающие проекты реализую с помощью packet by feature в domain
ну давайте теперь будем вообще делать один класс для всех сущностей и репозиториев, че мелочится то
Спасибо за статью, интересно увидеть альтернативный взгляд.
Но не смотря на то, что структура по идее соответствуем идеям DDD и в целом аналогична как минимум примерам Вон Вернона, я считаю что в реальности от неё может быть больше проблем чем пользы. Поэтому постараюсь конструктивно расписать в чём я вижу минусы:
Основной минус - сложность для понимания
Вы утверждаете что с такой структурой проще понимать доменную логику:
Мы видим из чего состоит предметная область нашего контекста. Мы в принципе видим предметную область :D а не группы типов из которых она состоит.
Я бы сказал что наоборот. Когда вы анализируете код проекта(и не важно потому что вы разработчик который только пришёл на него, или например архитектор который собирается его декомпозировать), то вам не настолько принципиально какие там доменные ивенты или ВО. Самое главное это понять какие есть агрегейт руты и энтити и дальше уже копать вглубь, чтобы понять как модуль/сабдомен взаимодействует с остальными или какие фичи он реализует и тд. И я не согласен с утверждением что отталкиваться нужно от фич:
Нам не интересно ProductPrice это VO или Entity, нам важно какую бизнес задачу решает этот объект. Мы видим бизнес-фичу "Назначить продукт на шаблон" и все её внутренние состовляющие которые она в себе содержит
Хоть это и звучит логично и бизнес-ориентированно, на практике фич могут быть десятки и сотни, а вот модель – одна. Поэтому важно сначала понять модель, а потом уже переходить к фичам. Если же ваш пакет состоит из сотен классов, то вы убьёте уйму времени просматривая классы которые вам даже не нужны для начала.
В том же EventStorming-е есть типы "ивент" и "агрегат" которые разделены по формам и цветам, чтобы это было нагляднее, поэтому по хорошему даже бизнес должен понимать что это разные типы. Да, можно сказать что в итоге они всё равно находятся все на одной доске чтобы описать бизнес процесс, но в отличие от доски со стикерами в пакете кода вы так сразу не разберётесь что есть что и как они взаимодействуют.
Минус второй - сложность анализа
Помимо этого, намного проще визуально анализировать сложность доменного слоя когда он разделён на чёткие слои - например если у нас 100 сущностей и 5 VO, скорее всего мы делаем что-то не так. Суда же относятся и кол-во доменных сервисов. Вы в статье утвержаете что доменный сервис и фича это одно и тоже:
Фичи (aka доменный сервис) именуем в повелительном падеже: Сделай что-то. Создай товар. Обнови цену. Проверь имя на уникальность. Посчитай очки популярности.
Но это неверно – я даже полез к разным DDD-авторам перепроверить. Все утверждают что доменный сервис это скорее исключение из правил нужное для того чтобы работать с несколькими агрегатами, и которым не нужно злоупотреблять.
Вот например из книги Эванса:
“When a significant process or transformation in the domain is not a natural responsibility of an ENTITY or VALUE OBJECT, add an operation to the model as a standalone interface declared as a SERVICE. Define the interface in terms of the language of the model and make sure the operation name is part of the UBIQUITOUS LANGUAGE. Make the SERVICE stateless.”
...
"SERVICES should be used judiciously and not allowed to strip the ENTITIES and VALUE OBJECTS of all their behavior."
Поэтому для меня кол-во доменных сервисов это зачастую показатель либо того насколько связаны агрегейт руты, либо что модель выраждается и логика начинает писаться не в сущностях и VO. (У нас в DDD-проектах их например почти нет, и каждый раз когда они собираются появиться мы стараемся анализировать нужны ли они вообще).
Можно конечно решать эти проблемы соответствующими аннотациями(т.е. помечать например @ValueObject или @AggregateRoot) или интерфейсами + fitness functions, но имхо это сложнее реализовать, менее наглядно, не всем нужно и не позволяет проводить визуальный анализ.
Алтернативный пример
Вот пример структуры которую использую я в монолитах:
/domain
/product
/entity
/service
/value_object
/event
/another module/subdomainЕсли же у вас микросервисы и действие уже происходит в условном product-service, то можно обойтись без вложенных пакетов, что будет практически тем же самым что и в ваших примерах в начале.
PS: Репозитории я вообще не включаю в доменный слой, тк считаю что даже доменные сервисы не должны иметь зависимостей(хоть DDD это и не запрещает), чтобы не смешивать бизнес и инфра-логику.
Ну и напоследок
В целом я считаю, что оба подхода имеют место быть, хоть и вижу в вашем/классическом больше минусов, по причинам описанным выше. При этом намного важнее я считаю:
Заставить разработчиков вникать в бизнес. Если они это делают то они и с анемичной моделью будут фокусироваться на бизнесе и использовать стратегические паттерны DDD. А если нет, то никакие тактические паттерны DDD вам не помогут, скорее наоборот.
Следить за чистотой кода и доменной области. Не позволять разным агрегатам напрямую ссылаться друг на друга, только по id. Не мешать доменную и инфра-логику. Не создавать изменяемые VO. Сделать рефакторинг постоянным процессом и тд.
Иметь единую структуру пакетов и тактических паттернов в рамках проекта и организации. Если вы планируете проекты которые будут расти и поддерживаться годами то стандартизация имеет огромное значение. Куда важнее чтобы структура пакетов была одинаковой в разных модулях/сервисах, чем то, чтобы она была "правильнее", но только в некоторых. И чтобы у всех было понимание какие тактические паттерны используются на проекте – чтобы не было ситуаций когда кто-то использует анемичную модель, кто-то DDD, кто-то смесь и тд.
Спасибо за отличный комментарий.
Я наоборот же в своей практике сталкивался с тем, что когда мы имеем контекст с большим доменом - класическая структура по типам начинает вставлять палки в колеса.
/domain
/product
/entity
/service
/value_object
/event
/another module/subdomainПредставьте, что product состоит из нескольких агрегатов и множества ентити, мы получим в папке entity много файлов и много подпапок. и это повториться в других папках: enity, value_object, event, service, exception и тд. И что бы понять какие у нас есть сущности и какие ивенты например эти сущности создают - нам придется пробежаться по каждой вложенной папке. На практике это зачастую приводило к тому что уже сделаны какие-то выводы по инвестигейту будущей работы и потом оказалось, что разработчик забыл посмотреть в какую-нибудь fooBar папку/подпапку и выбранное решение уже не подходит т.к. мы незаметили часть картины домена.
И чем больше контекст/домен - то болше будет вложенных папок/подпапок и собрать какое-то понимание того что происходит в домене становится затруднительно. Нам приходится закрывать папку domain и открывать папку application и уже изучать юзкейсы которых обычно еще больше и пропустить что-то становится еще более просто.
Так же про сабдомены:
/domain
/product
/another module/subdomainВ данной картине очень сложно понять кто рут домен а кто сапортящий. Приходится так же открывать каждый из них и изучать, а иногда становится не очевидно кто же первее "курица или яйцо". product или template.
Представьте, что product состоит из нескольких агрегатов и множества ентити, мы получим в папке entity много файлов и много подпапок
Если говорить только про кол-во файлов – не совсем понимаю, а как это отличается от вашего подхода? В нём по идее ещё больше файлов будет в корневом пакете, тк энтити, VO и тд на одном уровне. А про подпапки я вообще ничего не говорил :D
В данной картине очень сложно понять кто рут домен а кто сапортящий
Почему же? Я считаю что сабдомены должны быть на одном уровне и общаться друг с другом по хорошему только через интерфейсы-API. На сколько я помню ни Эванс ни Вон Вернон не говорили про вложенные сабдомены. Тот же Влад Хононов в книге с обезьяной предлагает дробить сабдомены на более гранулярные например, и иметь плоскую структуру вместо вложенной если это неоходимо. Даже если мы решаем что нам могут быть нужны вложенные сабдомены, то имхо это должно обрабатываться по другому. Если это монолит - то делать его модульным и как минимум верхнеуровневые сабдомены делать отдельными модулями. Если же это микросервисы то тут вообще не нужно много думать - либо у вас 1 (верхнеуровневый) домен/сабдомен на микросервис, либо опять же делаете его модульным.
Т.е. имхо не должно быть такого:
/core_subdomain
/supporting_nested_subdomain
some classes of core subdomainЕсли уж нужны вложенные сабдомены, то делать что-то вроде такого:
/core_subdomain
/supporting_nested_subdomain
/supporting_nested_subdomain_client
/core_nested subdomain -> depends on supporting_nested_subdomain_client, not supporting_nested_subdomainНу а вообще стоит проанализировать точно ли это вообще сабдомены или может просто модули.
Здесь у вас произошла ошибка с определением понятиями. Подходы взаимодействия через интерфейсы и ТД(pub/sub, partnership, separated ways, shared, etc) - это какраз все про взаимодействие контекстов, а не доменов.
В рамках одного контекста только сам контекст (бизнес) диктует какие домены в нем существуют и как они взаимодействуют друг что бы решать свои задачи. Зачастую у каждого контекста есть основной домен без которого контекст не несёт пользы бизнесу, а все остальные домены - вспомогающие. (На моей практике встретился только один контекст который состоял из трёх сабдоменов которые ничего друг о друге не знали)
Подходы взаимодействия через интерфейсы и ТД(pub/sub, partnership, separated ways, shared, etc) - это какраз все про взаимодействие контекстов, а не доменов.
Тут как раз таки скорее у вас ошибка. Во первых важно не путать концепты интеграции БК такие как partnership, separated ways, conformist, с реализациями типа pub/sub или синхронный вызов/sync API – но я в целом не про них говорил. Я говорю про взаимодействие сабдоменов ВНУТРИ БК(Bounded Context). Подходы через интерфейсы и тд никак напрямую не зависят от интеграций БК. Если вы условно создаёте модульный монолит который состоит из одного БК, то ваши модули всё равно будут взаимодействовать через те-же интерфейсы. По хорошему все авторы сходятся во мнение что 1 БК в идеале должен быть 1 сабдоменом. Если же говорить чуть более приземлённо, то в рамках одного БК может быть несколько саб-доменов и их взаимодействие никак не относится к интеграции БК.
Вот пример чуть нагляднее – представьте что у вас 1 БК, но 2 микросервиса(никто не запрещает), один реализует supporting сабдомен, другой core. Как они будут у вас общаться? Так или иначе через API(и не важно синхронный или ассинхронный). Так почему тогда в рамках монолита всё должно лежать в 1 пакете и иметь иерархичную структуру? И как вы тогда если что сможете их в случае нужды декомпозировать на микросервисы?
Когда мы делаем модульный монолит - каждый его модуль это отдельный бк. И вот модули (БК) между собой должны общаться абстрактно через интерфейсы/апи. А вот внутри одного БК может происходить что угодно главное не вылезая за пределы самого себя.
Посмотрите на моем примере:
У нас в одном БК есть два домена(продукт и шаблон). Где то там живёт отдельный бк с доменом шаблон который занимается шаблонами и этот БК кормит всех остальных своими данными.
И любой БК который как-то использует шаблон - имеет в себе сабдомен шаблона с той моделью данных которая нужна именно ему - только наполняет он ее из основного БК шаблонов.
Очень сложно представить ситуацию где кто то захочет сабдомен шаблона вынести отдельно инфраструктурно) получится что сервис шаблон-продукта(СПб домен) ходит в сервис шаблонов, а сервис продуктов ходит в сервис шаблон-продукта. Это даже не бумаге выглядит ужасно. Но на вкус и цвет все фломастеры разные.
Выносить кусок контекста(модуля) отдельно я думаю разумно только по каким-то производительным/инфраструктурным причинам.
А вот внутри одного БК может происходить что угодно главное не вылезая за пределы самого себя.
Я говорю про взаимодействие сабдоменов ВНУТРИ БК(Bounded Context).
Я соглашусь с вами что ваша точка имеет место быть, но на моей практике - к распилу контекста на несколько сервисов прибегают в самый последний момент когда уже явно что то пошло не так. Самый правдоподобный сценарий для этого "со времён мы выяснили что это два разных контекста".
важно не путать концепты интеграции БК такие как partnership, separated ways, conformist, с реализациями типа pub/sub или синхронный вызов/sync API
Здесь чувствуется смешивание единых языков DDD и простых технических вещей :D pub/sub это не про очереди и не про инфраструктурную реализацию в контексте DDD)
publisher/subscriber или customer/supplier - это про то, какой контекст является поставщиком знаний/услуг/фичей а какой контекст является потребителем.
Так же про минусы - на моей практике внедрения данной структуры минус понимания типов (какие сущности у нас есть) существует только первый месяц +- пока команда не перестраивает своё мышление. Это нормально для всего чего-то нового и отличного от того как мы привыкли делать. Мозг лентяй и он часто сопротивляется.
С практикой применения данной структуры данные вопросы так же легко закрываются. Мы видим существительное и понимаем что это ентити или часть ентити. А solution architect обычно вообще не прибегает к коду и делегирует эти задачи команде разработки, он больше манипулирует другими вещами - такими как ендпоинты/схема бд и тд. А т.к. наши агрегаты/ентити это != схема бд здесь тоже можно начать заблуждаться в полученных выводах.
А solution architect обычно вообще не прибегает к коду и делегирует эти задачи команде разработки, он больше манипулирует другими вещами - такими как ендпоинты/схема бд и тд
Ну это уже от архитектора зависит. Я вот, как software architect очень даже манипулирую :D
При условной декомпозиции монолита схема бд мне мало что скажет. Условно в бд можеть быть всё связано через foreign keys, но в реальности нет никаких общих транзакций и связей, а значит можно спокойно дропать foreign keys и декомпозировать сервис. Ну и наоборот тоже может быть.
Эндпоинты тоже не особо много могут сказать, тем более их может быть огромное кол-во.
А вот доменная модель и слой аппликейшн/доменных сервисов может сказать очень многое, особенно если у нас DDD. К примеру может оказаться что все агрегаты супер сильно связаны и разделять их будет болезненно. Или что несколько агрегатов из разных сабдоменов должны быть консистентны, а значит нам либо придётся оставлять эти сабдомены вместе, либо придумывать какие нибудь SAGA или распределенные транзакции со всеми вытекающими.
Ну и в конце концов основной способ декомпозиции сервисов это именно по сабдоменам, а для этого нужен анализ этих самых сабдоменов, и желательно не только на уровне бизнеса, но и кода.
Репозитории я вообще не включаю в доменный слой, т.к. считаю что даже доменные сервисы не должны иметь зависимостей (хоть DDD это и не запрещает), чтобы не смешивать бизнес и инфра-логику.
Наконец-то в первый раз встречаю мнение сходное с моим. Начиная с первых книг по ддд репозитории постоянно упоминаются в составе функционала, связанного с доменом. Но это же совсем разные вещи. С доменом связаны доменная логика и модель данных домена. Репозитории работают с персистентными данными и персистентной моделью данных. И как раз книги по ддд говорят о необходимости разделения функционала домена и функционала работающего с базами данных.
Не позволять разным агрегатам напрямую ссылаться друг на друга, только по id.
В моём понимании агрегаты вообще не должны знать о существовании друг друга даже если это вызов другого агрегата по его id. Если необходимо работать с несколькими агрегатами, то это делает или доменный сервис или use case из которого идёт вызов логики домена.
Много мы общаемся здесь на смежные темы и одна вытекает из другой, очень интересно но вы как самый активный комментатор статьи ответьте на вопрос: появилось у вас желание попробовать данный подход структурирования кода на каком нибудь новом проекте/пет-проекте или все никуда кроме структуризации по типам не уйдете?
Структура кода в папке Domain по DDD