分析应用程序并标识分解边界
为了将应用程序移到微服务体系结构,Fabrikam 需要评估其当前的应用程序,并确定每个微服务的范围和边界。 对于此评估,他们将使用领域驱动设计 (DDD) 框架。 我们来看看如何将它应用到应用程序。
注意
本文章未完整介绍全面的域分析。 我们有意精简了示例,以演示要点。 有关 DDD 的更多信息,请参阅本模块末尾总结中的“了解更多”部分。
什么是领域驱动设计?
DDD 是一种系统设计方法,最初由 Erik Evans 在 2005 年的书籍《领域驱动设计:软件核心复杂性应对之道》中提出。 此方法包含三个关键元素:
- 侧重于核心领域和领域逻辑。
- 根据领域的模型构建设计。
- 推动技术团队与业务合作伙伴之间的迭代协作,从而不断改进系统。
DDD 提供一个框架,可让你顺利访问一组设计完善的微服务。 它包括两个不同的阶段:战略和战术。 在 DDD 的战略模式中,定义系统的大规模结构。 战略模式有助于确保体系结构专注于业务功能。 策略性 DDD 提供一组可用于创建域模型的设计模式。 这些模式包括实体、聚合和领域服务。 借助这些战术模式,可以设计低耦合且具内聚性的微服务。
在 DDD 的战略阶段,绘制业务领域的关系图,并定义领域模型的边界上下文。 在战术 DDD 阶段,需要更精确地定义领域模型。 战术模式在单个边界上下文中应用。 在微服务体系结构中,我们要关注的是实体和聚合模式。 应用这些模式有助于标识应用程序中服务的自然边界。 作为一般原则,微服务应不小于聚合,且不大于边界上下文。
对于高级别操作,可将此过程分解为四个步骤:
- 分析业务领域,以了解应用程序的功能要求。 该步骤输出领域的非正式说明,可将其优化成更正式的一组领域模型。
- 定义领域的边界上下文。 每个边界上下文包含一个领域模型,该模型表示较大应用程序的特定子域。
- 在边界上下文中,应用战术 DDD 模式以定义实体、聚合和域服务。
- 通过使用前一步骤的结果来标识应用程序中的微服务。
仔细查看每个步骤中发生的情况。
分析业务领域
DDD 模式首先对业务领域进行建模,然后创建领域模型。 领域模型是业务领域的抽象模型。 它可以提取和组织领域知识,并为开发人员和领域专家提供通用语言。
首先,映射所有业务功能及它们之间的连接。 此分析是一项涉及到领域专家、软件架构师和其他利益干系人的协作性工作。 无需使用任何特定的形式。 可以直接草绘或者在白板上绘制关系图。
在填充关系图时,可以开始标识离散的子域。 哪些功能密切相关? 哪些功能是业务的核心?哪些功能提供辅助服务? 什么是依赖项关系图? 在此初始阶段,不需要考虑技术或实施细节。 也就是说,应该注意应用程序需要在哪个位置与 CRM、付款处理或计费系统等外部系统集成。
定义边界上下文
领域模型包含现实世界中事物的表示形式,如用户、无人机、包裹等。 但这并不意味着系统的每个部分都需要对相同的事物使用相同的表示形式。
例如,处理无人机维修和预测分析的子系统需要表示无人机的许多物理特征。 这些特征包括维护历史、里程、使用年数、型号和性能详细信息。 但是,在安排交付时,我们并不需要关心这些方面。 计划子系统只需知道无人机是否可用,以及取件和交货的预计到达时间 (ETA)。
如果尝试为这两个子系统创建单个模型,则会不必要地增大复杂性。 此外,模型会更难得到发展,因为任何更改都需要满足处理不同子系统的多个团队的要求。 通常,更好的做法是设计不同的模型,用于在两种不同的上下文中呈现相同的真实实体(在本例中为无人机)。 每个模型仅包含其特定上下文中相关的功能和属性。
在此方法中,边界上下文的 DDD 概念可以派上用场。 边界上下文只是应用特定领域模型的领域中的边界。 在上图中,我们可以根据各种功能是否共享单个领域模型将功能分组。
定义实体、聚合和服务
在战术 DDD 阶段,需要更精确地定义领域模型。 战术模式在单个边界上下文中应用。 在微服务体系结构中,我们要关注的是实体和聚合模式。 应用这些模式有助于标识应用程序中服务的自然边界。 作为一般原则,微服务应不小于聚合,且不大于边界上下文。
需要考虑多个战术 DDD 模式:
- 实体:实体是一直保持唯一标识的对象。 例如,在银行应用程序中,客户和帐户就是实体。
- 值对象:值对象没有标识。 其属性值定义了它,是不可变的。 值对象的典型示例包括颜色、日期时间和货币值。
- 聚合:聚合定义一个或多个实体的一致性边界。 聚合的作用是为事务不变性建模。 现实世界中的事物具有复杂的关系。 客户创建订单,订单包含产品,产品有供应商,等等。 如果应用程序修改了多个相关对象,它如何保证一致性? 如何跟踪并实施不变性?
- 领域服务和应用程序服务:在 DDD 术语中,服务是实现某种逻辑且不保存任何状态的对象。 Evans 对封装领域逻辑的领域服务,以及提供技术功能的应用服务做了区分。 应用程序服务通常包括技术功能,例如用户身份验证或发送短信。 领域服务通常用于对跨多个实体的行为建模。
- 领域事件:发生某种情况时,可以使用领域事件来通知系统的其他部件。 顾名思义,领域事件应该表示领域中发生的某些情况。 例如,“在表中插入了记录”不是领域事件。 “已取消交付”是领域事件。 领域事件与微服务体系结构密切相关。 由于微服务为分发式且不共享数据存储,领域事件可为微服务提供相互协调的途径。
Fabrikam 开发团队在其系统中确定了以下实体:
- 交付
- 包裹
- 无人机
- 帐户
- 确认
- 通知
- 标记
前四个实体(“交付”、“包裹”、“无人机”和“帐户”)都是表示事务一致性边界的聚合。 “确认”和“通知”是“交付”的子实体。 “标记”是“包裹”的子实体。
此设计中的值对象包括“位置”、“ETA”、“包裹重量”和“包裹大小”。
有两个领域事件:
- 当无人机起飞时,“无人机”实体将发送 DroneStatus 事件,用于描述无人机的位置和状态,例如,飞行中、已着陆。
- 每当交付阶段发生更改时,“交付”实体将发送 DeliveryTracking 事件。 这些事件包括 DeliveryCreated、DeliveryRescheduled、DeliveryHeadedToDropoff 和 DeliveryCompleted。
请注意,这些事件描述领域模型中有意义的事物。 它们描述有关领域的某些信息,但不与特定的编程语言构造相关。
开发团队还确定了另一个功能领域,但该功能领域并不与前面所述的任何实体紧密相关。 系统的某个部分必须协调有关安排或更新交付的所有步骤。 开发团队在设计中添加了两个领域服务。 计划程序将协调这些步骤。 监控器监视每个步骤的状态,以检测是否有任何步骤失败或超时。
识别微服务
现在,我们可以从领域模型转到应用程序设计。 下面是一个用于从域模型派生微服务的方法。
- 从限定上下文开始。 通常,微服务中的功能不应跨多个限定上下文。 根据定义,边界上下文标记特定领域模型的边界。 如果微服务将不同的域模型混合在一起,则表明可能需要优化域分析。
- 接下来,查看领域模型中的聚合。 聚合通常是微服务的适当候选项。 合理设计的聚合能够体现一个设计优良的微服务的许多特征:
- 聚合派生自业务要求,而不是数据访问或消息传递等技术因素。
- 聚合应具有较高的功能内聚性。
- 聚合是持久性的边界。
- 聚合应为松散耦合。
- 域服务也是微服务的适当候选项。 域服务是跨多个聚合的无状态操作。 典型的示例是涉及多个微服务的工作流。 稍后我们会看到无人机交付应用程序中的域服务示例。
- 最后,考虑非功能性要求。 分析团队规模、数据类型、技术、可伸缩性要求、可用性要求和安全要求等因素。 这些因素可能导致需要进一步将微服务分解成两个(或更多个)较小服务,或执行相反的操作,将多个微服务合并成一个。
务必追求实用性,并记住领域驱动设计是一个迭代过程。 如果有疑问,可以从更粗粒度的微服务入手。 将微服务拆分成两个较小服务比跨多个现有微服务来重构功能更容易。
将领域驱动设计应用到无人机应用程序
对于 Fabrikam 的应用程序,所有这些服务都驻留在其现有的整体应用程序中。 确定可以在何处将应用程序分解为微服务之后,接下来他们将从包服务开始。
包服务当前有一个专门开发团队,表现出了与可伸缩性相关的性能问题,是开始分解应用程序的有利候选位置。