1.3.3 聚合与聚合根、聚合内部实体
在笔者看来,聚合(Aggregate)是DDD在战术层面最为重要的一个概念。它是DDD可以在战术层面应对软件核心复杂性的关键。
什么是聚合?聚合在对象之间,特别是实体与实体之间划出边界。聚合内的实体分为两种:聚合根(Aggregate Root)与聚合内部实体(或者非聚合根实体)。
在本书的行文中可能会使用以下几种说法:
·聚合内实体,指聚合内包括聚合根的那些实体,一般不会把聚合根排除在外。具体含义读者可以根据上下文判断。
·聚合内部实体,把聚合根排除在外的聚合内部的实体。
·非聚合根实体,非常明确地把聚合根排除在外的聚合内部的实体。需要避免产生歧义时会使用这个说法。
一个聚合只能包含一个聚合根。当客户端需要访问一个聚合内部实体的状态时,最先能得到的只有聚合根,然后通过这个聚合根,才能进一步访问到聚合内的其他实体。
从一个聚合根出发能够访问到的实体可以认为是一个整体。聚合内部实体的生命周期由它们所属的聚合根控制。如果聚合根不存在,那么在它控制下的聚合内部实体也就不存在了。
很多时候,一个聚合内只有聚合根这一个实体。有人声称,从经验上判断,大约有70%的聚合只有一个实体。
实体又被称为引用对象,这个名称与值对象的概念相对。
本书中提到聚合根、实体、值对象的时候,多数情况下并不会特别说明是指这些对象的“类型”还是指它们的“实例”,请读者自行根据上下文判断。
我们应该把一个聚合根的实例以及生命周期完全受它控制的那些聚合内部实体的实例作为一个整体来看待,对于这样一个整体,有时候书中会使用“聚合实例”这个说法。
为了更好地理解什么是聚合、聚合根、聚合内部实体,下面举例说明。这个例子包含订单(Order)、订单头(OrderHeader)、订单行项(OrderItem)三个互相关联的概念。
想象我们在给一个电商系统构建订单相关的模型,我们可能会得到:
·一个叫作Order的聚合。
·这个订单聚合的聚合根是一个叫作OrderHeader的实体。实体OrderHeader的ID叫作OrderId(订单号)。
·通过OrderHeader实体,我们可以访问OrderItem实体的一个集合。OrderItem这个实体的局部ID叫作ProductId(产品ID)。因为业务逻辑可能已经明确不允许在同一个订单的不同订单行项内出现同一个产品,所以我们可以选择产品ID作为订单行项的局部ID(Local ID)。
显然,在这里,Order聚合与OrderHeader聚合根的名字并不相同,这体现了聚合与聚合根这两个概念的微妙区别。但是确实在大多数时候,聚合和它的聚合根拥有同样的名字,甚至在大多数时候,一个聚合内就只有聚合根这一个实体。