Я хотел бы написать приложение что-то вроде электронной коммерции.
И вы знаете, что в аналогичных приложениях продукты могут иметь разные свойства и особенности. Чтобы смоделировать такую возможность, я создал следующие сущности модели предметной области:
Категория - это что-то вроде «электроника> компьютеры», то есть виды товаров. Категории содержат список свойств (List <Property>).
Свойство - независимый объект, который содержит имя, единицы измерения, тип данных. Например, «имя», «вес», «размер экрана». Одно и то же свойство может иметь разные продукты.
Продукт - просто содержит имя и список значений, относящихся к свойствам. Значение - это объект, который содержит только поле значения и идентификатор поля свойства.
Первоначально я решил сделать Категорию как отдельный агрегат в этой схеме, потому что, например, когда я добавляю новый продукт, мне нужно знать все данные, связанные с текущей категорией, включая свойства, связанные с текущей категорией ( category.AddNewProduct (product) ). Но что мне делать, когда мне просто нужно добавить новое свойство, которое не относится ни к одной категории. Например, я не могу сделать эту категорию. AddNewProperty (свойство), потому что он четко говорит, что мы добавляем свойство к определенной категории.
Хорошо, следующим шагом я решил разделить Property в отдельный агрегат, но тогда это будет список с простыми сущностями.
Конечно, я могу создать что-то вроде PropertyAggregate, чтобы сохранить внутренний список свойств и бизнес-правил, но когда я добавляю продукт, мне нужно иметь внутри категории весь список свойств, принадлежащих этой категории, чтобы проверить инварианты. Но я также знаю, что хранить ссылки внутри агрегата на других агрегатах - плохая практика.
Какие варианты дизайна этого бизнес-кейса?
Ответы:
В перспективе 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
, вам больше не разрешается переходить непосредственно от aProperty
к соответствующим значениям. Это ограничение навигации может быть показано на диаграмме UML:Обратите внимание , что эта конструкция не мешает вам иметь
List<Property>
инCategory
, со ссылкой семантической (например , Java): каждая ссылка в списке относится к разделяемомуProperty
объекту в хранилище.Единственная проблема с этим дизайном состоит в том, что вы можете изменить
Property
или удалить его: поскольку он находится за пределами агрегата, агрегат не может заботиться о согласованности своих инвариантов. Но это не проблема. Это является следствием принципов DDD и сложности реального мира. Вот цитата из Эрика Эванса в его основополагающей книге « Проектирование на основе доменов: борьба со сложностями в основе программного обеспечения »:Так что да, если вы измените a
Property
, вам нужно убедиться, что служба проверяет, что категории, ссылающиеся на нее, обновляются по мере необходимости.Вариант 3: категория, имущество и продукт в разных совокупностях
Мне просто интересно, если предположение, что
Product
принадлежит одномуCategory
, основано:Product
под несколькимиCategories
. Например, в категории «Ноутбуки» и в категории «Компьютеры» вы найдете «Ноутбук торговой марки X Model Y» и «Многофункциональный принтер Z» в категориях «Принтер», «Сканер» и «Факс».Product
сначала создает , а только потом присваивает его категориям и заполняет значения?Это не упростит агрегаты, и у вас будет еще больше правил, охватывающих агрегаты. Но ваша система будет гораздо более перспективной.
источник
Property
за пределыCategory
агрегата, значит ли это, что онProperty
становится агрегатом сам по себе и нуждается в хранилище? Если это правда, то как передать требуемыйList<Property>
вCategory
экземпляр? Через конструктор? Это будет правильно? И как мне узнать списокProperty
идентификаторов,Category
которые еще не были созданы?Feature
и она будет принадлежать толькоProduct
. и эта организация не будет участвовать в поиске. что ты говоришь ?На мой взгляд, вы можете решить это одним из двух способов:
Категория - это особый вид товара
Это означает, что для любого продукта в вашей базе данных он содержит внешний ключ, указывающий на тот же самый продукт таблицы. Продукт является продуктом, только если не существует продуктов, внешний ключ которых равен идентификатору указанного продукта. Другими словами, если у него нет продуктов под ним, это продукт.
Это немного упростит ситуацию. Продукты к свойствам будут иметь отношение один ко многим, и поэтому ваши категории будут иметь отношение один ко многим, поскольку они также являются продуктами. Добавить свойство к категории так же просто, как добавить свойство к продукту в вашей программе. Загрузка всех свойств будет означать объединение свойств продукта со свойствами соответствующего продукта категории и до тех пор, пока вы не достигнете продукта категории без родителя.
Ваше приложение электронной коммерции должно было бы провести это различие, но если вы все равно можете загружать продукты категории, это не потеря производительности, чтобы знать, имеете ли вы дело с категорией или продуктом. Это также хорошо подходит для поиска в древовидной моде на уровне продукта, так как каждый продукт (категория) может открыть список субпродуктов без особой дополнительной работы.
Недостатком этого является, конечно, дополнительная информация, присутствующая в продукте, которая не имеет смысла для категории, что может создать неудобные неиспользуемые поля в продукте. Хотя это решение будет более гибким в вашем приложении, оно также несколько менее интуитивно понятно.
Отношение ко многим
Продукты больше не находятся в сложных отношениях с собственностью. Вы создаете таблицу ProductProperty с внешними ключами таблицы продуктов и таблицы свойств, которые связывают их. Аналогично, у вас есть таблица категорий с отношением многие-ко-многим с таблицей свойств и таблица CategoryProperty с внешними ключами таблицы категорий и таблицы свойств.
Сам продукт будет иметь отношение «многие к одному» с категорией, что позволит вам по существу создать список уникальных свойств, относящихся как к продукту, так и к категории, с помощью хорошо формализованного оператора выбора.
С точки зрения базы данных это определенно чище и более гибко. Ваше приложение, вероятно, может по большей части обходиться без непосредственного обращения к CategoryProperty или ProductProperty, если запрос выполняется правильно. Однако вы не должны относиться к категории или продукту как к владельцу собственности. Это должна быть отдельная сущность в вашей программе. Это также означает, что управление указанными свойствами будет зависеть от создания самого свойства, а затем связывания его с категорией или продуктом в два отдельных этапа. Конечно, больше работы, чем первое решение, но отнюдь не сложнее.
В дополнение к этому вам также придется выполнить дополнительную проверку при удалении категории или продукта, если какие-либо из его свойств используются другими (в отличие от первого решения, в котором вы могли бы безопасно удалить все связанные свойства данного продукта / категории) ,
Вывод
В профессиональном контексте я бы выбрал дополнительную милю и дистанцию из продукта и продукта из собственности, используя подход «многие ко многим». Там не было бы возможности перекрытия данных, и в некотором смысле, легче рассматривать каждый из этих трех как свою собственную сущность. Однако ни в коем случае это не первое плохое решение, поскольку оно также позволяет вам написать более простое приложение. Просто знайте, что если вы подумали, что в конечном итоге вам, возможно, понадобится перейти от одного решения к другому, вам, скорее всего, будет лучше выбрать второе.
Удачи!
источник