DDD / Агрегаты в .NET - PullRequest
       35

DDD / Агрегаты в .NET

6 голосов
/ 06 июля 2011

Я читал книгу Эванса по DDD и думаю о том, как реализовать агрегаты в .NET.В настоящее время я могу придумать только один способ;выделение агрегатов в отдельных библиотеках классов.Это, однако, выглядит немного излишним (я бы предпочел хранить все доменные объекты в одной библиотеке), и мне интересно, есть ли другой способ?

Причина 1 lib / aggregate такова:Следующее: Совокупный корень должен знать обо всем доступе к «подобъектам», за которые он отвечает, также сводный корень может возвращать подобъекты в качестве результатов своих членов.Следовательно, члены (необходимые для совокупного корня) этих подобъектов не могут быть обнародованы.Таким образом, ваш единственный вариант - сделать их внутренними (поскольку они все еще должны вызываться совокупным корнем).Однако, поместив все агрегаты в один проект, все еще можно получить доступ к этим членам из других объектов домена, которые получили подобъект.Это нежелательно, поскольку позволяет обойти корень совокупности.Путем разделения всех агрегатов в разных библиотеках эта проблема решается.

Некоторая дополнительная информация:

Я проверил пример кода Java DDD , и они упаковывают каждый агрегат (включая все классы подобъектов) в другой пакет.Члены, которые могут быть вызваны только из совокупного корня, не имеют модификатора доступа (например: Delivery.updateOnRouting).В java членами без модификатора доступа являются package-private (доступно только из того же пакета).Так что это будет правильное поведение.

Пример кода .NET , однако, помещает все доменные объекты в одну библиотеку классов, а затем делает соответствующие члены общедоступными.Мне это кажется неправильным.

Ответы [ 5 ]

6 голосов
/ 07 июля 2011

Агрегаты являются одним из самых сложных понятий в DDD. У вас есть большая часть этого права. Я предлагаю выразить концепцию в терминах «членство» в совокупности более просто, чем ввести термин «подобъекты».

Да, объект не может быть членом более чем одного агрегата. Какой из них будет последним исполнителем? Один агрегатный корень может легко сделать недействительным другой, удалив элементы и оставив других членов в другом агрегате. Вы правы, в сценариях, где объект может нуждаться в членстве в нескольких агрегатах, этот объект должен быть независимым объектом, то есть он становится корнем нового агрегата. (У него могут быть или не быть дополнительные члены, но если его нет, то, конечно, он становится собственным агрегатом одного.)

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

Однако, и это тонкая и трудная часть, эта конечная ответственность НЕ подразумевает, что совокупность также является первичным исполнителем. Очень похоже на то, что мы имеем в судебной системе - суд, в конечном счете, является конечной площадкой, где определяются вопросы права и где навязывается окончательная верховенство права, инвариант исполняется. Но фактическое применение (и соблюдение) происходит на многих уровнях системы. На самом деле в хорошо организованном обществе большинство действий, навязывающих верховенство закона - обеспечение соблюдения инвариантов, должно происходить задолго до того, как вы попадете в суд, и вам даже не придется полагаться на обычное обращение в суд. (Хотя в DDD вам всегда может потребоваться, чтобы агрегатный корень выполнял окончательную инвариантную очистку, например, перед сохранением.)

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

Давайте посмотрим, что произойдет с вашим доменом, если вы последуете предложенному пути. Цель состоит в том, чтобы создать богатую и выразительную модель предметной области. С точки зрения осмысленного вездесущего языка, вы сократили свой рабочий словарный запас только до совокупных корней. Предполагается, что к объекту должен обращаться агрегатный корень из-за инвариантов, а также потому, что если дизайн верен, сущность имеет значимую идентичность, которая вытекает из его членства в контексте его агрегатного корня. Но ваше предложение, что у сущности даже нет идентификатора типа вне его совокупного корня. Эванс, в частности, говорит, что это является частью цели совокупного корня - позволить объектам получать ссылки на члены путем обхода. Но вы не можете получить полезную ссылку, потому что другой объект даже не знает, что существуют ваши типы членов. Или вы можете изменить пространство имен, но это не лучше, если вы не разрешите обход. Теперь весь ваш домен знает о типах, но о типах объектов, которые невозможно получить.

Еще хуже то, что происходит с вашим совокупным корнем. Агрегированный корень обычно должен иметь собственную причину существования, в дополнение к сохранению агрегированной целостности. Но теперь эта личность уже не ясна. Это скрыто необходимостью иметь метод-обертку для всех различных элементов и их атрибутов. То, что вы получаете, - это совокупные корни, которые больше не имеют выразительности или даже четкой идентичности, а только большие громоздкие объекты Бога.

Ваш пример Order и OrderLine - интересный пример.Приказ не обеспечивает принудительное применение какого-либо инварианта, требуемого OrderLine от имени OrderLine.В этом случае он контролирует действие, чтобы реализовать свой собственный инвариант.Это правильное действие, чтобы поставить под контроль совокупный корень.Однако более типичные агрегаты в основном связаны с созданием и / или уничтожением объектов.

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

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

Надеюсь, это поможет, это сложная концепция для обсуждения.

2 голосов
/ 06 июля 2011
Therefore, members (needed by the aggregate root) of these sub-objects can't be made public.

Я бы предположил, что этот вывод слишком жесткий, чтобы быть практичным, и Эванс не выступает за него.Да, он говорит, что Agg Root отвечает за создание и доступ к другим объектам в этом корне, НО

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

Во-вторых, я думаю (моя интерпретация и у меня нет книги!)идея Agg Root в отношении доступа к объектам является скорее соглашением, чем догмой.Догма состоит в том, чтобы удовлетворять запросы клиентов в контексте этого корня, чтобы упростить домен.Это скорее вопрос разработки интерфейса для Aggregate Root, а затем чтобы клиенты проходили через этот интерфейс для удовлетворения своих потребностей.Если вы можете ограничить доступ к объектам, которые (любому) клиентам не нужны (с использованием какого-либо Агрегированного корня), сделайте это всеми способами.

HTH,
Berryl

2 голосов
/ 06 июля 2011

Помимо (интересного) теоретического аспекта вашего вопроса, я думаю, что практический ответ на ваш вопрос заключается в использовании пространств имен для разделения агрегатов.В .NET вы помещаете свои классы в пространства имен.Структура пространства имен не зависит от структуры проекта, и вы можете поместить несколько пространств имен в один проект, что при компиляции приводит к одной сборке.

Вы можете использовать это, чтобы поместить все ваши доменные классы в одну сборку и все ещеесть разделение между ними.

2 голосов
/ 06 июля 2011

Я могу придумать только один путь;выделение агрегатов в отдельных библиотеках классов.Это, однако, выглядит немного излишним

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

0 голосов
/ 06 июля 2011

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

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

...