领域驱动设计(3) DDD设计流程

DDD整体包含战略设计和战术设计两部分。战略设计过程会从业务视角出发,进行场景分析、领域建模,并划分领域边界、建立通用语言、确定限界上下文;战术设计则关注如何将模型转化为软件实现,涉及聚合根、实体、值对象、领域服务、应用服务等概念。所以战略设计重在把控方向、建立模型,战术设计重在软件实现,战略设计的好坏直接决定了DDD能否成功实施。所以除了关注DDD战术设计中用到的工具集、思想(比如四层架构、CQRS等)之外,更应该关注其战略设计。

DDD也是协作方式的变革,在战略设计、战术设计过程中,需要业务、技术双方角色的共同参与,比如领域专家、业务需求方、产品经理、架构师、开发经理、测试经理等。
下面以一个请假、考勤系统的案例来贯穿DDD的整个设计流程。系统的功能包括:

  1. 请假:请假人可以提交请假单,根据请假人的身份、请假类型、请假天数进行校验,然后根据审批规则逐级提交给上次审批,审批通过后记录考勤,否则请假申请被拒绝;
  2. 考勤:根据考勤规则,核销请假数据,对请假数据进行校验,输出考勤统计。

战略设计

战略设计的目的是根据用户旅程分析,找出领域对象和聚合根,对实体和值对象进行聚类组成聚合,划分限界上下文,建立领域模型。
战略设计采用事件风暴的方法,包括产品愿景和场景分析、领域建模、微服务拆分(如果要实现为微服务的话)等过程。

产品愿景与场景分析

产品愿景是对产品顶层价值的设计,避免产品偏离方向。事件风暴中,大家讨论的维度可以围绕产品目标用户、核心价值、差异化竞争点。如果所涉及的系统目标和需求非常明确,那么这一步可以忽略。

场景分析则是从用户的视角出发,探索业务领域中的典型场景,明确涉及的场景、每个场景的用例。然后根据不同角色的场景分析,尽可能全面地梳理从前端操作到后端业务逻辑发生的所有操作、命令、领域事件以及外部依赖关系。

以请假和审批为例:请假的用户(角色)为请假人,审批的用户(角色)为审批人。
作为请假人,其用户旅程为:

  • 登录系统,从鉴权中心获取请假人信息和权限数据,完成登录认证;
  • 创建请假单:在请假页面选择请假类型、起始时间,录入请假休息,保存请假单;
  • 修改请假单:查询请假单,修改、保存;
  • 提交审批:将创建的请假单提交审批,系统会获取审批规则,并根据审批规则,从人员组织关系中获取审批人,给请假单分配审批人。

作为审批人,其用户旅程为:

  • 登录系统,从鉴权中心获取请假人信息和权限数据,完成登录认证;
  • 获取请假单:获取审批人名下的请假单;
  • 审批:填写审批意见,批准或者拒绝;
  • 逐级审批:审批人批准请假单后,系统会根据审批规则判断是否需要继续审批;
  • 完成审批:系统发出领域事件:请假单已审批通过,后续会进一步执行邮件通知请假人、将请假数据发送到考勤的操作。

基于上述用户旅程进行场景分析,可以用不同的颜色来区分命令、业务流、事件、说明信息。
以业务流为主线,梳理出请假、审批的流程,然后基于业务流的每个步骤,梳理出触发这个步骤的命令,步骤完成后,会产生的事件。最终的参考结果如下图:
场景分析
这张图中,“根据审批规则查询审批人”需要依赖另一个上下文:人员组织关系,它的场景分析如下:
场景分析1

领域建模

领域建模,会根据场景分析梳理出的事件、命令,找出实体、聚合根、限界上下文,最终建立领域模型。
如果说场景分析是一个发散的过程,各种角色各抒己见,大家一起分析用户旅程;那么领域建模就是一个收敛的过程。在这一步中,会由对DDD有足够了解的架构师、开发人员来主导完成。而且见仁见智,收敛的结果很可能因人而异,不过建模结果并没有绝对的好坏,只要能适应当前场景,而且模型还会在后续的知识循环、迭代中不断完善,最终不断逼近最优解。
领域建模可以按这三个步骤进行:

  1. 找出实体、值对象等领域对象;
  2. 将这些领域对象形成聚合,确定聚合根;
  3. 根据业务、语义边界等因素,定义限界上下文;

找出领域对象

场景分析梳理出了事件、命令,接下来就要分析并找出产生这些命令、领域事件的实体和值对象。
下图为在请假、审批系统找出的实体、值对象(图中都是绿色):
找出实体

定义聚合

定义聚合前,先找出聚合根,聚合根可以管理聚合中的实体,所以可以将请假单、人员作为两个聚合根;然后找出与聚合根紧密依赖的实体和值对象,请假单聚合根下包含审批意见、审批规则,人员聚合根下包含组织关系。
那么剩下的刷卡明细、考勤明细、考勤统计该怎么办呢,这也是一类典型的场景,即几个实体、值对象之间相互独立,找不出聚合根,却又共同完成一项功能,具有很高的业务内聚性。对于这种情况,仍然可以采用DDD的分析设计方法,将这些实体、值对象组成一个没有聚合根的聚合,但由于没有聚合根,实体的管理就交给领域服务了。
于是现成了请假、人员、考勤三个聚合:
形成聚合
其中请假聚合中,可以将审批规则作为值对象,因为它是来自人员聚合的副本;人员聚合中,人员和组织关系都作为实体,没有值对象;考勤聚合也没有值对象。
实际执行中,可以用一张表格整理这些聚合中的领域对象,标记它们分别是实体还是值对象.

定义限界上下文

人员组织关系聚合和请假聚合共同完成请假的业务功能,所以可以将两者划分为请假限界上下文;考勤聚合单独构成考勤限界上下文。

微服务拆分

限界上下文可以作为粗粒度的微服务边界,但落地时往往不得不考虑更多其他因素,比如弹性边界、安全需求、软件包大小、团队沟通效率、技术异构等等。
这个案例中我们直接将限界上下文作为微服务边界。

战术设计

以上,战略设计部分就完成了。在战略设计过程中,进行了场景分析,建立了领域模型,确定了微服务边界。接下来开始战术设计过程。

分析微服务领域对象

领域模型包含很多领域对象,如何把这些领域对象与代码设计结构相对应,是这个战术设计关注的重点。这一步会细化领域对象,有助于发现事件风暴过程中可能遗漏的业务、技术细节。
具体步骤为:

  1. 根据命令设计应用服务,确定应用服务的功能,服务集合、编排方式。服务集合中的服务包括领域服务或其它微服务的应用服务;
  2. 根据应用服务功能要求设计领域服务,定义领域服务;
  3. 根据领域服务的功能,确定领域服务内的实体以及功能;
  4. 设计实体的基本属性和方法。

以提交审批这个动作为例,提交审批的过程为:

  1. 根据请假类型、时长,查询请假审批规则,获取下一步审批人的角色;
  2. 根据审批角色从人员组织关系中查询下一审批人;
  3. 为请假单分配审批人,并将请假规则保存到请假单;

设计应用服务、领域服务

首先进行第1、2步;基于以上流程,需要设计以下服务、方法:

  • 应用层:提交审批应用服务;
  • 领域层:
    • 请假聚合:查询审批规则、修改请假流程信息服务。请假单实体提供修改请假流程信息方法,审批规则值对象有查询审批规则方法;
    • 人员聚合:根据规则查询审批人服务;人员实体提供根据审批规则查询审批人方法。

整体关系如下图:
服务设计

设计聚合中的对象

在请假单聚合中,请假单是聚合根。请假单经多级审批后,会产生多条审批意见,为了便于查询,可以将审批意见设计为实体。请假审批通过后,会产生请假审批通过的领域事件,因此还会有请假事件实体。
接下来分析请假单聚合中的值对象。请假人和下一审批人数据来源于人员组织关系聚合中的人员实体,可以设计为值对象。人员类型、请假类型、审批状态时枚举值类型,可以设计为值对象;请假审批规则也可作为值对象。
综上,请假聚合对象关系图如下:
请假聚合关系图
人员组织关系聚合中,聚合根为人员,实体为组织关系,组织关系实体中包括设计审批领导、组织关系类似,两者都可以设计为值对象。
人员聚合

在确定各聚合领域对象的属性后,可以接着设计各领域对象在代码模型中的代码对象,建立领域对象对代码对象的映射关系。这个过程可以选择用表格的形式,来确定对象所属的命名空间、类名,以及方法名等等,就不再详述了。

设计微服务代码结构

应用层代码结构

应用层包括应用服务、DTO以及事件发布相关的代码。
应用层代码结构

领域层代码结构

在领域层,一个聚合对应一个聚合代码目录,聚合之间在代码上完全隔离,只能通过应用层交互。这样做的好处是控制耦合,如果日后需要拆分微服务,则可以直接将聚合独立出来,再对应用层稍加改造就可以了。
领域层代码结构

参考资料: 欧创新 《DDD实战课》

posted @ 2022-04-10 12:06  zhixin9001  阅读(1155)  评论(0编辑  收藏  举报