如何设计总体边界?


10

我想编写一个类似电子商务的应用程序。

您知道,在类似的应用程序中,产品可能具有不同的属性和功能。为了模拟这种机会,我创建了以下域模型实体:

类别 -这类似于“电子产品>计算机”,即产品类型。分类包含属性列表(List <属性>)。

属性 -包含名称,度量单位,数据类型的独立实体。例如“名称”,“重量”,“屏幕尺寸”。同一属性可以具有不同的产品。

产品 -仅包含名称和与属性有关的值列表。值是一个仅包含值字段和属性的字段ID的对象。

我最初决定在此方案中使Category像单个聚合一样,因为例如当我添加新产品时,我需要知道与当前类别有关的所有数据,包括与当前类别有关的属性(category.AddNewProduct(product))。但是,当我只需要添加不属于任何类别的新属性时该怎么办。例如,我不能执行此category.AddNewProperty(property),因为它明确表示我们将属性添加到特定类别。

好的,下一步我决定将Property划分为单独的聚合,但是它将是一个包含简单实体的列表。

当然,我可以创建诸如PropertyAggregate之类的内容以保留在属性和业务规则列表中,但是当我添加产品时,我需要在类别内具有属于该类别的属性的整个列表,以检查不变量。但是我也意识到,将链接内的链接保留在其他聚合上是一种不好的做法。

设计此业务案例有哪些选择?


您能否提供有关类别,属性和产品的更完整示例?电子产品或计算机将是一类,iPhone X将是产品的一个例子,而财产将是什么呢?11英寸显示屏?
尼尔(Neil)

你几乎是对的。我添加了一些说明
cephei

看来您只是从“数据容器”的角度看聚合设计。您可能还需要考虑应用程序的用例,并考虑到事务性方面,协作/并发访问,发生的事件,状态转换等
。– guillaume31

Answers:


7

在DDD透视图中CategoryProductProperty是实体:它们都对应于具有自己标识的对象。

选项1:您的原始设计

您建立Category了单个聚合的根。一方面,这是有道理的,因为在修改其对象时,聚合应确保一致性,并且Product必须具有以下Properties内容Category

在此处输入图片说明

但是另一方面,单一聚合意味着其所有对象都与拥有它们的根相关,并且所有外部引用都必须通过该聚合根进行。这意味着:

  • 一个具体Product属于一个并且只有一个Category。如果Category删除,则其也将删除Products
  • 一个特定的Property事物属于一个并且只有一个Category。否则,如果“电视屏幕”和“计算机监视器”将是两个类别,则“电视屏幕:尺寸”和“计算机监视器:尺寸”将是两个不同的属性。

第二点与您的叙述不符:“ 但是当我只需要添加一个Property不属于任何类别的新内容时,我该怎么办 ”。尚不清楚是否Properties可以在不同的地方使用相同的东西Categories

选项2:总资产以外的财产

如果a Property独立于而存在Categories,则它必须在集合之外。如果您想在Properties两者之间共享Categories(对于高度,宽度,大小等有意义),也是如此。看来确实是这样。

结果是在Property和属于聚合的事物之间的链接上:尽管您可以从聚合的内部导航到Property,但不再允许您直接从a Property转到对应的值。此可导航性限制可以在UML图中显示:

在此处输入图片说明

请注意,这种设计不会阻止你有一个List<Property>Category,语义上(如JAVA):列表中的每个基准是指可共享的Property存储库中的对象。

这种设计的唯一问题是您可以更改a Property或将其删除:由于它不在聚合中,因此聚合无法照顾其不变式的一致性。但这不是问题。这是DDD原则和现实世界的复杂性的结果。以下是Eric Evans在他的开创性著作《域驱动设计:解决软件核心中的复杂性》中的一句话:

跨越AGGREGATES的任何规则都不应始终保持最新。通过事件处理,批处理或其他更新机制,可以在指定的时间内解决其他依赖性。但是,在每次交易完成后,将强制在AGGREGATE中应用不变式。

因此,是的,如果您更改,则Property必须确保服务会检查引用它的类别是否已根据需要进行更新。

选项3:不同汇总中的类别,属性和产品

我只是想知道是否建立了a Product属于一个单一的假设Category

  • 我经常看到网上商店提出Product以下建议Categories。例如,您会在“笔记本电脑”类别和“计算机”类别下找到“笔记本电脑X型Y笔记本电脑”,在“打印机”,“扫描仪”和“传真”类别下找到“多功能打印机Z”。
  • 有人创建Product第一个,然后稍后才将其分配给Category并填充值,这是不可能吗?
  • 如果要拆分类别,是否真的要删除其产品,然后在新类别下重新创建它们?

它不会简化聚合,您将拥有更多跨聚合的规则。但是您的系统将提供更多的未来证明。


非常感谢,这是一个非常有用的解释。但我想澄清几点,我想从第二种选择开始,谁知道,也许我会谈到第三种。如果我Property超出了Category集合的界限,这是否意味着它Property本身就变成了一个集合并且需要一个存储库?如果是真的,那么如何将required传递List<Property>Category实例?通过构造函数?是吗?以及如何找出尚未创建的PropertyID 的列表Category
cephei

简而言之@zetetic:是的,您需要一个独立的Property存储库。您可以将现有属性的列表传递给Category的工厂,或者创建空的Categories并使用addProperty方法填充该列表。相应的问题:假设您要具有“强制”和“可选”属性,并且强制特征取决于类别。您将如何处理?
Christophe

回答您的问题时,首先想到的是我可以创建一个特殊实体,Feature并且该实体仅属于Product。并且该实体将不会参与搜索。你说什么 ?
cephei

@zetetic为什么不呢!我将保留产品中当前的值,并将该功能与类别相关联。一个类别具有n个特征(是其集合的一部分),一个Property定义了m个特征(但链接通过Category-> feature进行)。然后,您已将多对多关系分解为更易于管理的元素,从而阐明了总边界。最后,关于库注:如果您通过标识引用其它聚集这不是必需的(读这篇文章informit.com/articles/article.aspx?p=2020371&seqNum=4
克里斯托夫

5

如我所见,您可以通过以下两种方法之一解决此问题:

类别是一种特殊类型的产品

这意味着对于数据库中的任何给定产品,它都包含指向同一表产品的外键。仅当不存在外键等于该产品ID的产品时,该产品才是产品。换句话说,如果它下面没有产品,那么它就是产品。

这样会简化一些事情。产品与属性之间存在一对多的关系,因此您的类别也与产品具有一对多的关系。将属性添加到类别就像在程序中将属性添加到产品一样容易。加载所有属性将意味着将产品的属性与其关联的类别产品的属性组合起来,直到找到没有父项的类别产品为止。

您的电子商务应用程序需要对此加以区分,但是如果您仍然可能加载某个类别的产品,那么知道您要处理的是类别还是产品就不会降低性能。它也很适合以树的形式搜索产品级别,因为每个产品(类别)都可以打开子产品列表,而无需进行过多的工作。

不利的一面当然是产品中存在多余的信息,这些信息对于类别没有意义,会在产品中创建尴尬的未使用字段。尽管此解决方案在您的应用程序中会更灵活,但也不太直观。

多对多关系

产品不再与财产形成复合关系。您使用链接产品表和属性表的外键创建一个ProductProperty表。同样,您有一个与属性表具有多对多关系的类别表,以及一个具有类别表和属性表的外键的CategoryProperty表。

产品本身与类别具有多对一关系,因此您可以通过格式正确的select语句从本质上创建与产品和类别相关的唯一属性列表。

从数据库的角度来看,这绝对是更干净,更灵活的。如果查询正确完成,则您的应用程序可能在很大程度上无需直接处理CategoryProperty或ProductProperty。但是,您也不应该将类别或产品视为财产所有者。它应该是您程序中自己的实体。这也意味着对这些属性的管理将是创建属性本身,然后在两个单独的步骤中将其与类别或产品相关联。当然,比第一种解决方案要花更多的工夫,但绝没有难度。

除此之外,如果其他人正在使用类别或产品的任何属性,您还必须在删除类别或产品时执行其他检查(与第一个解决方案一样,您可以安全地消除给定产品/类别的所有相关属性) 。

结论

在专业背景下,我将使用多对多方法,将产品和财产产品的距离加倍。数据不会重叠,从某种意义上说,将这三个中的每一个作为自己的实体比较容易。但是,第一个解决方案绝不是一个糟糕的解决方案,因为它还使您可以编写一个更简单的应用程序。只是知道,如果您认为您最终可能需要从一种解决方案转向另一种解决方案,那么选择第二种解决方案可能只是您的最大利益。

祝好运!


感谢您提供详细而有趣的答案!在数据库级别,正如您在第二种情况中所解释的那样,我已经对它进行了建模,这种模式称为实体-属性-值,但是我被困在代码级别,即聚合的定义。在大多数情况下,所有这些实体都一起使用。可以将其合并为一个聚合,但是在某些情况下,就像填充目录一样,从某种意义上说,这些目录被从聚合中淘汰了。
cephei
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.