一、聚合
在领域驱动设计(DDD)中,聚合是一个核心概念,它帮助开发者管理复杂性,特别是在处理大量相关对象时。
聚合是由紧密关联的实体和值对象组成,是修改和保存数据的基本单位。每个聚合都有一个仓库,用于保存聚合的数据。
聚合有一个聚合根和上下文边界,边界根据业务需求和内聚原则,定义了聚合应该包含哪些实体和值对象,而聚合之间是松耦合的,这样设计的微服务,会很自然地实现高内聚、低耦合。
聚合在 DDD 分层架构中是领域层的一部分,领域层可以包含多个聚合,共同实现核心业务逻辑。实体在聚合内以充血模型实现业务能力,保证业务逻辑的高内聚。
跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务逻辑通过应用服务来实现。
二、聚合根
在传统数据模型中,每个实体都是独立的。如果随意修改和调用实体,可能会导致数据逻辑不一致。使用锁的方式会使软件变得复杂,降低系统性能。
可以把聚合根看作是部门的负责人,它不仅是实体,也是这个聚合“部门”的管理者。
聚合根作为实体时,有自己的属性和业务行为,执行自己的业务逻辑。
当聚合根作为管理者,它在聚合内部协调实体和值对象,按照固定的业务规则完成业务逻辑。
当聚合之间协作时,聚合根是对外的接口人。它通过自己的ID关联其他聚合,接受外部请求。如果需要访问聚合内的其他实体,必须先访问聚合根,然后导航到聚合的内部实体。外部对象不能直接访问聚合内实体。
三、聚合的一些设计原则
设计聚合时,遵循一些核心原则可以确保聚合的有效性、一致性。
1. 选择合适的聚合根
每个聚合都应有一个聚合根,它是聚合中最重要的实体,其他实体和值对象通过聚合根关联。聚合根应该是能够代表整个聚合的实体,它负责保证聚合内部数据的一致性和完整性。选择聚合根时,应考虑哪个实体在业务中扮演核心角色,并且能有效地管理和封装聚合的行为。
2. 聚合最小化
聚合应尽可能地小,只包含必须由聚合根直接管理的实体和值对象。这样可以降低聚合内部的复杂性,提高聚合的内聚性,小的聚合更容易理解和维护。
3. 封装业务规则
聚合根应该封装其聚合内部的业务规则,任何对聚合内部数据的修改,都应通过聚合根的方法进行访问,方法执行后应保证所有业务规则都被正确执行。通过这种方式,聚合根可以确保聚合的状态始终一致。
4. 聚合间的引用通过标识符
聚合之间不应直接引用对方的实体,而应该通过标识符(如ID)进行引用。这样做有助于减少聚合间的耦合,使得各个聚合可以独立地进行变更和扩展,而不会影响到其他聚合。
5. 一致性边界的定义
设计聚合时,要明确哪些操作必须是强一致的,即在完成操作后,聚合的状态必须是一致,这涉及到事务操作。聚合应该是最小的一致性边界,任何事务应当只在单个聚合的范围内完成,不应跨聚合操作。
四、如何设计和使用聚合和聚合根
我们以电商平台的订单系统为例,看看聚合和聚合根是如何设计和使用的。
在订单系统中,订单通常作为聚合根,因为订单是整个业务流程中最核心的业务对象。
订单聚合包括多个实体对象,如订单明细、支付信息、收货信息等。
订单明细:聚合内的实体,每个订单明细代表订单中的一个购买项。它包括商品ID、购买数量、单价和小计。订单明细与订单紧密关联,其生命周期由订单聚合根管理。
支付信息:也是聚合内的实体,支付信息记录了支付方式(如信用卡、支付宝、微信支付)、支付状态、支付金额和支付时间等,这些信息对于完成交易和进行财务核算至关重要。
收货信息:通常是一个值对象,包含省、城市、街道和邮编等信息。因为它没有独立的标识,仅仅描述了一个地理位置。
通过设计正确的聚合,订单系统的业务操作会非常清晰,并能够集中管理。
下面列举一些常见的业务操作,介绍聚合是如何被使用的。
1. 订单创建操作
当客户选完商品,并提交订单时,系统会触发订单创建的流程。
系统首先创建一个新的订单聚合实例,此实例以订单为聚合根。订单聚合根包含了必要的信息,如订单编号、订单初始状态等。
客户选定的每个商品都会作为订单明细,添加到订单中。每个订单明细实体包括商品ID、购买数量和单价等信息。这些订单明细在创建过程中由订单聚合根动态管理,确保数据的完整性。
客户提供的收货地址被创建为值对象,并与订单聚合关联。同时,初始化的支付信息也会被设置为一个实体,包括支付方式和支付状态等信息。
2. 支付处理
当客户进行支付时,支付信息实体会被更新,包括记录支付金额、支付方式、支付时间等。订单聚合根确保支付信息与订单的状态保持一致性。
支付成功后,订单聚合根会将订单状态更新为“已支付”,这是通过聚合内部的业务逻辑完成,确保所有数据一致。
3 .发货处理
在订单准备发货时,订单聚合根会验证存储的收货地址信息的完整性和准确性。如果地址不完整,可能会要求客户提供更多信息,或进行二次确认。
一旦发货地址验证无误,且商品准备就绪,订单聚合根将订单状态更新为“已发货”。随后,实际物流操作开始进行,并在系统中记录和跟踪发货的过程信息。