DDD中, 业务对象设计成聚合还是值对象, 这是一个问题

200 阅读6分钟

前篇文章介绍了聚合的作用,也简单介绍了一下聚合的组成。这次分析一下聚合的设计,是设计成聚合还是值对象?

聚合和聚合根

聚合的组成包括了实体和值对象,取其中一个实体作为聚合的聚合根。想修改这个聚合里面的内容,必须通过聚合来修改,聚合内部对外应该是黑盒的存在。

聚合像一棵树,而树的顶点就是聚合根。所以有时候说这个对象是聚合根,是一个聚合,是一个实体,都没问题。从聚合外部看是一个聚合,从聚合内部看是一个聚合根,也是一个特殊的实体。

image.png

实体和值对象

实体和值对象的设计是非此即彼的,没有中间状态,因此放在一起对比能更好的理解他们的差异。

实体是具有一个唯一标识的,就是我们常说的 id,即使里面的属性发生变化,还是同一个对象。而值对象是没有唯一标识的,没有id,里面属性的组合就是唯一标识,只要其中属性发生变化,就是一个新的对象。

例如有一台电脑,有开关机状态,使用时长。开关机状态和使用时长是会变化的,但电脑还是那台电脑,因此应设计成实体。又例如电脑的费用,包含了金额和货币单位,不需要标识哪一个费用,只要金额或者货币单位其中一个发生变化,这个费用就会发生变化。

从例子可以看出一般有生命周期的业务对象应设计成实体,而一些常见的属性,用于描述某种特性的,例如颜色,金额,重量等等,一般设计成值对象。

设计成实体还是值对象并不是根据业务对象本身来设计的,而是需要结合上下文,有时还存在两种都可以的情况。一般来说我们会偏向设计成值对象,因为值对象更加简单,不需要维护状态,且事务安全。如果是实体,例如你要修改里面的值,需要先获取原来的实体,修改里面的属性。而值对象则直接替换对象即可。

聚合还是值对象

聚合是一个特殊的实体,因此聚合还是值对象的问题转化成了实体还是值对象,再考虑是否把实体设计成聚合。实体虽然并不等于聚合,但大多数场景实体会被设计成聚合。而值对象则肯定不会是聚合。

举个例子:一辆车里面的轮胎怎么设计,车已被设计成聚合。 这时会有 2个问题,是设计成实体还是值对象? 如果是实体是否应该设计成一个聚合?

有3种方案:

image.png

前面提到如何设计需要考虑上下文。不同上下文的设计也不同。

汽车销售部上下文

销售部的作用是把车卖出去,有2个业务特性。

  1. 并不关心是哪一条轮胎卖出去的,销售部关心的是车,而车胎只要型号,品牌对的上就行了。同一个型号品牌的轮胎换哪个都一样。
  2. 车和轮胎必须时刻保持 1/4 的关系。

因为轮胎只关心型号和品牌,不关心是具体哪一条轮胎,也就不需要一个id 做唯一标识,只需要用型号和品牌作为唯一标识就行了。可以设计成值对象。

且车和轮胎要保持 1/4 的关系,这个业务约束很强,证明这两个对象关联关系很强,很适合放到聚合里面。因为放到聚合里面的话每次获取聚合都可以对这个约束进行校验,保证业务拿到的就是没问题的聚合。如果是2个聚合,就需要借助领域服务来约束这两个聚合的关系,需要分别拿出2个聚合来校验,就会变得更难维护。

因此在这个上下文里面适合用方案1。

修车厂上下文

修车厂的作用是如果车胎漏气了,车主会开到修车厂,修车厂会把轮胎拆下来,检查并补胎,再安装上去。对于轮胎来说有2个业务特性。

  1. 这个轮胎修好就必须安回原车,不能说只要型号一样,或者品牌一样,就随便拿条轮胎来换。
  2. 车和轮胎并不是一直保持1比4的关系,轮胎拆下来之后,可能就会变成 1比3了。

因为车胎从车上拆下来到安装回去,不管里面的属性发生了什么变化,都应该还是那条轮胎。意味着轮胎是有唯一标识的,因此应设计成实体。也就是排除了方案1。

选择方案2还是方案3重点需要考虑实体之间的业务约束,实体之间的关联强弱和业务复杂度。

  • 业务约束:车和轮胎并没有很强的业务约束。因此会考虑聚合多一些。
  • 实体之间的关联强弱:轮胎的生命并不是随车的,车坏了以后,轮胎还能继续用,可以换到其他车子上面,因此和车的关系并没到"组合",在修车厂内车甚至可以没轮胎,因此只有"依赖"的关系,关系比较弱。
  • 业务复杂度:轮胎有自己的业务流程,例如对轮胎补胎,放气打气等。这些只有轮胎参与没有车参与。如果涉及成实体,和车一起作为一个聚合。因为轮胎只能通过车这个聚合来获取,修改。则会导致这些业务流程都需要和车关联起来,会让车这个聚合变得很复杂。
  • 技术考虑:因为聚合是整个进行获取和保存的,因此需考虑聚合过大是否会导致性能问题。是否有强一致性 (和业务约束类似)。

从上面的角度看,把轮胎单独设计成一个聚合会更合理一点,因此选择方案3。

二手车市场上下文

再考虑一个场景,如果是二手车市场,会选择哪个方案呢? 相比前面的销售上下文,二手车市场需要唯一标识那个轮胎,有业务约束,但轮胎又没单独的业务流程,则适合设计成方案 2。

总结

聚合的设计分2步

  1. 实体还是值对象。根据定义,基于是否需要唯一标识判断。
  2. 实体还是聚合。根据业务约束,对象间强弱关系,业务复杂度,技术考虑。