域驱动设计 (DDD) 反对为整个系统创建单一统一模型的想法:相反,它鼓励将系统划分为边界上下文,每个上下文都有自己的模型。 在 DDD 的战略阶段,我们要绘制业务领域的关系图,并定义领域模型的边界上下文。
在战术 DDD 阶段,需要更精确地定义领域模型。 战术模式在单个边界上下文中应用。 在微服务架构中,每个有界上下文都是一个微服务候选者,我们对实体和聚合模式特别感兴趣。 应用这些模式有助于标识应用程序中服务的自然边界(请参阅本系列的下一篇文章)。 作为一般原则,微服务应小于聚合,且不大于边界上下文。 首先,让我们了解战术模式。 然后,我们对无人机交付应用程序中的“交货”边界上下文应用这些模式。
战术模式概述
本部分提供战术 DDD 模式的简要概述,如果你已熟悉 DDD,则可以跳过本部分。 Eric Evans 著作的第 5 - 6 章,以及 Vaughn Vernon 的 Implementing Domain-Driven Design(实施域驱动的设计)一书中更详细地介绍了这些模式。
实体。 实体是一直保持唯一标识的对象。 例如,在银行应用程序中,客户和帐户就是实体。
- 实体在系统中有唯一的标识符,使用该标识符可以查找和检索该实体。 这并不意味着,该标识符始终直接向用户公开。 它可能是数据库中的 GUID 或主键。
- 一个标识可以跨多个边界上下文,并可能保留到应用程序生存期结束之后。 例如,银行帐号或政府颁发的身份证号不会与特定应用程序的生存期相关联。
- 实体的属性可随时变化。 例如,某人的姓名或地址可能有变化,但他(她)仍是同一个人。
- 一个实体可以包含对其他实体的引用。
值对象。 值对象没有标识。 它只由其属性值定义。 值对象也是不可变的。 若要更新值对象,始终需要创建一个新实例来替换旧实例。 值对象可以包含用于封装域逻辑的方法,但这些方法不应该给对象的状态产生负面影响。 值对象的典型示例包括颜色、日期时间和货币值。
聚合。 聚合定义一个或多个实体的一致性边界。 一个聚合只包含一个根实体。 可以使用根实体的标识符执行查找。 聚合中的其他任何实体是根的子级,由从根开始的后续指针引用。
聚合的作用是为事务不变性建模。 现实世界中的事物具有复杂的关系。 客户创建订单,订单包含产品,产品有供应商,等等。 如果应用程序修改了多个相关对象,它如何保证一致性? 如何跟踪并实施不变性?
传统应用程序通常使用数据库事务来实施一致性。 但是,在分布式应用程序中,这种做法通常不可行。 单个业务事务可能跨越多个数据存储、长时间运行,或者涉及第三方服务。 最终由应用程序而不是数据层来实施域所需的不可变性。 这就是要为聚合建模的目的。
注意
聚合可以包含单个实体且不包含子实体。 聚合的定义由事务边界确定。
领域服务和应用服务。 在 DDD 术语中,服务是实现某种逻辑且不保存任何状态的对象。 Evans 对封装领域逻辑的领域服务,以及提供技术功能(例如用户身份验证或发送短信)的应用服务做了区分。 领域服务通常用于对跨多个实体的行为建模。
注意
软件开发中广泛使用了“服务”一词。 此处的定义不直接与微服务相关。
领域事件。 发生某种情况时,可以使用领域事件来通知系统的其他部件。 顾名思义,领域事件应该表示领域中发生的某些情况。 例如,“在表中插入了记录”不是领域事件。 “已取消交付”是领域事件。 领域事件与微服务体系结构密切相关。 由于微服务为分发式且不共享数据存储,领域事件可为微服务提供相互协调的途径。 服务间通信一文更详细介绍了异步消息传递。
还有其他几种 DDD 模式未在此处列出,包括工厂、仓储和模块。 实施微服务时,这些模式可能十分有用;但是,在微服务之间设计边界时,它们作用不大。
无人机交付:应用模式
首先,我们探讨“交货”边界上下文必须处理的方案。
- 某个客户可以请求派遣无人机从已注册到无人机交付服务的公司取件。
- 寄件人生成了一个标记(条形码或 RFID)并粘贴在包裹上。
- 无人机将会收取包裹,然后将包裹从源位置交付到目标位置。
- 当客户安排交付时,系统将会根据路线信息、天气情况和历史数据提供 ETA。
- 当无人机起飞时,用户可以跟踪当前位置和最新的 ETA。
- 在无人机收取包裹之前,客户可以取消交付。
- 完成交付时,客户将收到通知。
- 寄件人可以请求客户提供签名或指纹形式的交付信息。
- 用户可以查找已完成交付的历史记录。
在这些方案中,开发团队确定了以下实体。
- 交付
- 包裹
- 无人机
- 帐户
- 确认
- 通知
- 标记
前四个项(“交付”、“包裹”、“无人机”和“帐户”)都是表示事务一致性边界的聚合。 “确认”和“通知”是“交付”的子实体,“标记”是“包裹”的子实体。
此设计中的值对象包括“位置”、“ETA”、“包裹重量”和“包裹大小”。
为便于演示,下面提供了“交付”聚合的 UML 关系图。 请注意,该聚合包含对其他聚合(包括“帐户”、“包裹”和“无人机”)的引用。
有两个领域事件:
当无人机起飞时,“无人机”实体将发送 DroneStatus 事件,用于描述无人机的位置和状态(飞行中、已着陆)。
每当交付阶段发生更改时,“交付”实体将发送 DeliveryTracking 事件。 这些事件包括 DeliveryCreated、DeliveryRescheduled、DeliveryHeadedToDropoff 和 DeliveryCompleted。
请注意,这些事件描述领域模型中有意义的事物。 它们描述有关域的某些信息,但不与特定的编程语言构造相关。
开发团队还确定了另一个功能领域,但该功能领域并不与前面所述的任何实体紧密相关。 系统的某个部分必须协调有关安排或更新交付的所有步骤。 因此,开发团队在设计中添加了两个域服务:一个计划程序(用于协调步骤),以及一个监督程序(用于监视每个步骤的状态,以检测是否有任何步骤失败或超时)。这是计划程序代理监督程序模式的一个变体。
后续步骤
下一步是定义每个微服务的边界。