Как спроектировать совокупные границы?

10

Я хотел бы написать приложение что-то вроде электронной коммерции.

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

Категория - это что-то вроде «электроника> компьютеры», то есть виды товаров. Категории содержат список свойств (List <Property>).

Свойство - независимый объект, который содержит имя, единицы измерения, тип данных. Например, «имя», «вес», «размер экрана». Одно и то же свойство может иметь разные продукты.

Продукт - просто содержит имя и список значений, относящихся к свойствам. Значение - это объект, который содержит только поле значения и идентификатор поля свойства.

Первоначально я решил сделать Категорию как отдельный агрегат в этой схеме, потому что, например, когда я добавляю новый продукт, мне нужно знать все данные, связанные с текущей категорией, включая свойства, связанные с текущей категорией ( category.AddNewProduct (product) ). Но что мне делать, когда мне просто нужно добавить новое свойство, которое не относится ни к одной категории. Например, я не могу сделать эту категорию. AddNewProperty (свойство), потому что он четко говорит, что мы добавляем свойство к определенной категории.

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

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

Какие варианты дизайна этого бизнес-кейса?

Цефея
источник
Не могли бы вы предоставить более полный пример категории, свойства и продукта? Электроника или компьютеры были бы категорией, iPhone X был бы примером продукта, а собственность была бы чем именно? 11 "дюймовый дисплей?
Нил
ты почти прав. Я добавил некоторые пояснения
cephei
Кажется, вы рассматриваете совокупный дизайн исключительно с точки зрения «контейнера данных». Вы также можете подумать о вариантах использования вашего приложения, принимая во внимание транзакционные аспекты, совместную работу / одновременный доступ, происходящие события, переходы состояний и т. Д.
guillaume31

Ответы:

7

В перспективе DDD, Category, Productи Propertyявляются юридическими лицами: все они соответствуют объектам , которые имеют свою собственную идентичность.

Вариант 1: ваш оригинальный дизайн

Вы сделали Categoryкорень одного агрегата. С одной стороны, это имеет смысл, потому что агрегат должен обеспечивать согласованность при изменении своих объектов и Productдолжен иметь Propertiesсвои Category:

введите описание изображения здесь

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

  • один конкретный Productпринадлежит одному и только одному Category. Если Categoryудаляется, то и его Products.
  • конкретное Propertyпринадлежит одному и только одному Category. Иначе говоря, если бы «экраны телевизоров» и «компьютерные мониторы» были бы двух категорий, «телевизионные экраны: размер» и «компьютерные мониторы: размер» были бы двумя различными свойствами.

Второй пункт не соответствует вашему повествованию: « Но что мне делать, когда мне просто нужно добавить новый Property, который не относится ни к одной категории ». И неясно, можно ли одно и то же Propertiesиспользовать в разных Categories.

Вариант 2: Собственность вне совокупности

Если Propertyсуществует независимо от Categories, он должен быть вне совокупности. И то же самое, если вы хотите поделиться Propertiesмежду ними Categories(что имеет смысл для высоты, ширины, размеров и т. Д.). Кажется, это так и есть.

Следствием этого является связь между Propertyвещами и вещами, которые принадлежат агрегату: в то время как вы можете перемещаться из внутренней части агрегата в Property, вам больше не разрешается переходить непосредственно от a Propertyк соответствующим значениям. Это ограничение навигации может быть показано на диаграмме UML:

введите описание изображения здесь

Обратите внимание , что эта конструкция не мешает вам иметь List<Property>ин Category, со ссылкой семантической (например , Java): каждая ссылка в списке относится к разделяемому Propertyобъекту в хранилище.

Единственная проблема с этим дизайном состоит в том, что вы можете изменить Propertyили удалить его: поскольку он находится за пределами агрегата, агрегат не может заботиться о согласованности своих инвариантов. Но это не проблема. Это является следствием принципов DDD и сложности реального мира. Вот цитата из Эрика Эванса в его основополагающей книге « Проектирование на основе доменов: борьба со сложностями в основе программного обеспечения »:

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

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

Вариант 3: категория, имущество и продукт в разных совокупностях

Мне просто интересно, если предположение, что Productпринадлежит одному Category, основано:

  • Я часто вижу интернет-магазины, предлагающие один Productпод несколькими Categories. Например, в категории «Ноутбуки» и в категории «Компьютеры» вы найдете «Ноутбук торговой марки X Model Y» и «Многофункциональный принтер Z» в категориях «Принтер», «Сканер» и «Факс».
  • Разве не возможно, что кто-то Productсначала создает , а только потом присваивает его категориям и заполняет значения?
  • Если вы хотите разделить категорию, действительно ли вы удалите ее продукты, а затем заново создадите их под новыми категориями?

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

Christophe
источник
Большое спасибо, это очень полезное объяснение. Но я хотел бы уточнить несколько моментов. Я хотел бы начать со второго варианта, и кто знает, может быть, я приду к третьему. Если я выхожу Propertyза пределы Categoryагрегата, значит ли это, что он Propertyстановится агрегатом сам по себе и нуждается в хранилище? Если это правда, то как передать требуемый List<Property>в Categoryэкземпляр? Через конструктор? Это будет правильно? И как мне узнать список Propertyидентификаторов, Categoryкоторые еще не были созданы?
Цефей
Короче говоря, @zetetic: да, вам понадобится независимый репозиторий свойств. Либо вы передаете список существующих свойств фабрике категории, либо создаете пустые категории и заполняете список методом addProperty. Вопрос в ответ: представьте, что вы хотите иметь «обязательные» и «необязательные» свойства, а обязательный признак зависит от категории. Как бы вы справились с этим?
Кристоф
Отвечая на ваш вопрос, первое, что приходит на ум, - я могу создать специальную сущность, Featureи она будет принадлежать только Product. и эта организация не будет участвовать в поиске. что ты говоришь ?
Цефей
@zetetic почему бы и нет! Я бы оставил значения, так как они в настоящее время находятся в продукте, и связал бы функцию с категорией. Категория имеет n объектов (часть их совокупности), свойство определяет m объектов (но ссылка проходит через функцию Category->). Затем вы разложили отношение «многие ко многим» на более управляемые элементы, уточнив совокупную границу. Наконец, о внедрении репозитория: это не требуется, если вы ссылаетесь на другие агрегаты по идентичности (прочитайте эту статью informit.com/articles/article.aspx?p=2020371&seqNum=4 )
Кристоф
5

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

Категория - это особый вид товара

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

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

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

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

Отношение ко многим

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

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

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

В дополнение к этому вам также придется выполнить дополнительную проверку при удалении категории или продукта, если какие-либо из его свойств используются другими (в отличие от первого решения, в котором вы могли бы безопасно удалить все связанные свойства данного продукта / категории) ,

Вывод

В профессиональном контексте я бы выбрал дополнительную милю и дистанцию ​​из продукта и продукта из собственности, используя подход «многие ко многим». Там не было бы возможности перекрытия данных, и в некотором смысле, легче рассматривать каждый из этих трех как свою собственную сущность. Однако ни в коем случае это не первое плохое решение, поскольку оно также позволяет вам написать более простое приложение. Просто знайте, что если вы подумали, что в конечном итоге вам, возможно, понадобится перейти от одного решения к другому, вам, скорее всего, будет лучше выбрать второе.

Удачи!

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