写点什么

DDD 领域驱动设计落地实践系列:战略设计和战术设计

  • 2021 年 11 月 29 日
  • 本文字数:4898 字

    阅读完需:约 16 分钟

DDD领域驱动设计落地实践系列:战略设计和战术设计

引言

通过前面的文章介绍,相信大家对于什么是 DDD 有了初步的了解,知道它是一种微服务的架构设计方法论,为我们解决如何建立领域模型,如何实现微服务划分等提供了方向和指导。但是对于如何具体落地使用 DDD,可能大家还是一脸懵 B 的状态,因此从本文开始以及后面的文章将对如何进行 DDD 落地进行详细的阐述。在这其中还是会涉及到 DDD 中的一些重要概念,原本想着在一篇文章中介绍所有的概念,但是我觉得,概念总是在它该出现的时候出现才会让大家印象深刻,否则这些概念只是死板的概念,我们不清楚他为什么出现以及可以解决什么问题。


DDD 大致实现过程

如下图所示,实现 DDD 落地大致需要经历这样三个阶段,即为业务分析-》战略设计-》战术设计,不同阶段的输出都是下一阶段的输入。业务分析阶段为战略设计输出经过统一语言描述的业务事件、业务逻辑以及业务分类,而战略设计阶段又为战术设计阶段输入领域模型以及边界上下文,方便其进行微服务拆分以及模型映射。下面我们分别看下这三个阶段到底都做了什么事情。

业务分析:在这个阶段需要集齐项目团队的成员主要包括领域专家、设计人员、开发人员等一起对业务问题域以及业务期望进行全面的梳理,厘请业务中的统一语言,在业务领域中发现领域事件、领域对象及其对应的领域行为,搞清楚他们各自的关联关系。

战略设计:通过 DDD 的理论,对业务进行领域划分构建领域模型,梳理出相应的限界上下文,通过统一的领域语言从战略层面进行领域划分以及构建领域模型。在构建领域模型的过程中需要梳理出对应的聚合、实体、以及值对象。

战术设计:以领域模型为战术设计的输入,以限界上下文作为微服务划分的边界进行微服务拆分,在每个微服务中进行领域分层,实现领域模型对于代码的映射,从而实现 DDD 的真正落地实施。

相关概念补充

在介绍战略设计和战术设计之前,我们先来弄清楚一些晦涩难懂的概念性的内容,这些概念看上去总是不明觉厉。我觉得 DDD 不容易入门的一个原因就是这些概念太不好理解了,即便是《领域驱动设计:软件核心复杂性应对之道》原著中的描述更加让人难以捉摸。要想理解这些概念性的内容,我们要思考为什么有这样的概念,以及它的出现是为了解决什么问题,在实际的上下文场景下是怎么运用的,我想只有这样我们对这些不明觉厉的东东才能有更加深刻的理解。我尽量结合一些实例来进行说明,方便大家的理解。


1、值对象

值对象看上去又是比较抽象的概念,越抽象的概念我们越要抓住最主要的脉络,才能理解。就值对象来说,我们可以把他理解没有 ID 的东东,什么叫没有 ID 呢?可以这么理解,当我们在外面吃饭的时候,在餐厅里面选位子落座,我们会关心这个桌子是哪里生成的,编码是什么以及规格是怎样的吗?显然不会,有空位坐就可以了。当然我们还是需要关注值对象上下文的,如刚才的例子,我们消费者并不关心坐哪个位子,但是商家是关心的,因为他要根据桌号来进行上菜以及收账。

再来举一个例子,在我们设计用户信息的时候,这其中就会包括用户的地址,用户的地址实际是由这个用户是哪个省的,哪个市的,哪个县的以及具体地址是什么、邮编是什么等等属性信息组成,那么这个地址信息在用户信息中实际就是一个值对象,他不需要唯一的 ID 来标识。


2、实体

和值对象不同的是,实体是有唯一标识的,这就好比我们每个人都会有身份证,身份证上面的身份证号码就是每个人的唯一标识。另外这个身份证号码会一直跟着我们不会改变,即使你改了名字户口状态变化,但是身份证号码是不会变化的,这也是实体的另一个特征就是它具有延续性。实体对应了业务对象的业务属性以及业务行为。


值对象以及实体都是领域模型中的领域对象,是组成领域模型的基础元素,一起实现领域内的最基本的核心领域逻辑。


3、聚合

又是一个看上去不好理解的概念,实际上用大白话来说,如果人是一个个不同的业务实体,那么社会中的不同组织、机构就是将对应技能的人聚集在一起发挥更大的业务价值以及完成更加复杂的业务行为的的集合。这就好比我们公司里面有种各样的部门,有人力部门负责招聘和薪酬、有销售部门负责营销、有研发部门负责产品研发。不同的部门实际就是不同的聚合。 聚合就是有业务关联关系的实体以及值对象的集合,通过实体、值对象以及各自之间的业务逻辑聚合在一起完成某个业务节点,我们就可以理解为聚合。我们可以根据业务的单一职责以及高内聚的的设计原来来进行聚合的划分。


4、聚合根

聚合的出现实际是一种业务单元,那它必定涉及到数据的持久化,如果在聚合中的任意实体都可以被外部进行数据修改,那么我们将很难保证聚合内的数据一致性。因此我们需要有一个数据输入修改的统一入口来保证聚合内的数据修改统一的符合聚合中的业务规则,因此出现了聚合根的概念。聚合根实际是就是一种实体,具备唯一标识,有独立的生命周期。但是他是特殊的实体,他有协调实体以及值对象完成业务逻辑的功能,好比是一个部门的负责人一样。一个聚合只有一个聚合根,聚合根在聚合之内采用引用依赖的方式对实体和值对象进行组织和协调,聚合根和聚合根之间通过唯一 id 进行聚合之间的协同。


战略设计

战略设计这四个字听起来有点高大上的感觉,感觉离我等 DS 比较远。实际上无论对于公司或者个人来说,都需要有个发展的目标以及指导方针,指引我们该向何方前进,这个指导性的内容,我们就可以称之为战略。那么在 DDD 领域,战略设计主要从业务角度出发,划分业务的领域边界,建立基于通用语言和业务上下文语义边界的限界上下文,实现业务领域模型的构建。使得限界上下文可以作为微服务拆分和设计的边界。因为必须先有边界清晰的领域模型以及上下文,我们才能设计出边界清晰的微服务。因此在战略设计阶段最重要的就是限界上下文以及领域建模。


通常在业务分析阶段我们把业务域的主体流程进行了梳理以及分析,同事将业务期望进行了定义。在进行战略设计的过程中,需要对业务领域进行全面的梳理,首先对业务进行领域切分,划分出业务的上下文边界,然后建立对应的限界上文中的领域模型,上下文边界和领域模型可以做诶微服务拆分拆分设计的输入,指导微服务落地时的拆分设计。其中业务中,对应的领域模型十分重要。那么我们该采用什么方法才能从复杂的业务领域中构建出高内聚低耦合的领域模型呢?我们可以通过业务分析以及领域建模两个阶段来实现领域模型的构建。


业务分析

1、事件风暴

通过事件风暴的方法可以实现快速分析和分解复杂的业务领域,分析并提取出对应的领域模型。事件风暴是早 2013 年提出来的,通过组织领域专家、以及项目团队成员在一起通过头脑风暴的方式,通过梳理业务领域中所有的领域事件以及领域事件的参与者以及输入。

参与者

主要包括领域专家、DDD 专家、架构师、产品经理、项目经理、开发人员以及测试人员等

实际活动

当把参与者集合在一起之后,大家需要通过头脑风暴的方式梳理当前的业务域问题。比如某个业务动作或者行为是否会触发下一个业务动作,这个动作(领域事件)的输入和输出是什么,是谁(实体)发出的什么动作(命令),触发了这个动作,这些我们都需要梳理清楚。


我们以大家最熟悉的电商系统来举个栗子,在电商业务中有一个重要的环节就是物流。因此物流是电商业务的重要业务子域。当用户下完订单之后,在仓储领域就需要陈诚对应的货物拣货单,系统根据拣货单中的货物给不同分区的仓内作业人员生成对应的拣货任务,作业人员根据对应的拣货任务进行货品的拣取,并放入相应的货物容器中。最终将所有订单商品在拣货完成后进行合流,进行校验,最终形成包裹再进行后续的配送流程。

那么在这个流程当中,我们就会涉及到的实体就有订单、拣货单、拣货任务、用户、容器等,对应的值对象为地址信息。领域事件为拣货单生成、拣货任务生成。

领域建模

在我看来,领域建模实际上是整个 DDD 领域驱动设计中最重要的环节。对上,好的领域模型说明对业务流程的梳理以及业务领域的抽象做得好。对下,高内聚低耦合的领域模型可以直接决定微服务设计的质量和水平。

1、找出实体和值对象

经过全盘的业务梳理之后,我们可以在业务对象中找到对应的实体以及值对象,这里面就会牵扯到哪些领域对象设计未实体,哪些领域对象设计为值对象。主要还是根据实际的业务特征来决定。

2、构建聚合

通过业务梳理,我们找到了业务下所有的实体以及值对象,接下来我们就要构建聚合了。在构建聚合之前,我们需要先从实体集合中找到聚合根,这就好比打仗的时候讲究擒贼先擒王,王擒到了之后,归属于下面的小兵就会乖乖就范了。

那么我们应该怎么判断一个实体它就是聚合根呢?主要是看这个实体是不是有专门的模块来进行维护,自身是不是有完整的独立的生命周期以及是不是有全局的唯一 ID。通过这几个判断条件我们很容易找到对应的聚合根,如下图所示,在仓内进行作业的任务,其中拣货单就是一个聚合根,满足上述的几个条件,同时可以将和其有业务关联的实体例如货物、拣货容器等归并到拣货单,最终形成拣货聚合。


3、划分聚合到边界上下文

首先我们需要将之前梳理出来的领域事件,事件流转的触发命令都全部罗列出来,在这个过程中提取出产生业务行为的对象,就是前文所说的实体。如前面所说的物流域,库存、容器。在这个过程中我们需要找出对应的实体以及值对象,同时在这些实体中找出聚合根,将存在存在紧密业务逻辑关系的聚合根、实体以及值对象划分到一起形成聚合,再根据之前划分的边界上下文将多个聚合滑到限界上下文中。

战术设计

不同于战略设计的高屋建瓴,战术设计是从实际的技术角度出发,它更加侧重于领域模型的技术实现,按照领域模型完成微服务的开发以及落地。从某种意义来说,战略设计实际就是战术设计的输入。在战术设计中会有聚合、聚合根、实体、值对象、领域服务、领域事件的代码实现,通过将这些领域对象映射到代码中实现设计以及系统的落地。在这个阶段,我们需要梳理微服务的边界以及边界内的领域对象以及它们之间的关系,并且可以实际的将这些领域模型确定在哪些代码模块中以及微服务分层中的具体位置。

通过 DDD 按照一定的规则对业务领域进行细分,是的问题范围可以限定在指定的边界中,因此我们可以在这个边界内建立领域模型,而后再通过代码实现领域模型,从而解决相应的业务问题。

因此在战术设计阶段,我们有个重要的事项需要去完成,一个是微服务的领域对象分析与边界划分,另一个是微服务的结构分层。

微服务领域对象分析与边界划分

在战略设计阶段,我们已经构建的业务领域模型,领域模型中包含了实体、值对象、聚合根。在上文中我们实际已经根据一些业务域进行了聚合的划分,那么在此处我们需要根据不同的聚合以及已有的限界上下文继续划分微服务。有的微服务会包含多个聚合,有的微服务只有一个聚合。


微服务结构分层

在传统的工程代码结构中,大都采用 MVC 的工程结构。但是随着微服务时代的来临,传统的工程结构不能满足快速变化的业务需求。另外随着 DDD 领域驱动设计的落地,需要对于微服务的工程结构有更进一步的进化和升级。


Eric Evans 在《领域驱动设计:软件核心复杂性应对之道》文中提出了传统的四层结构,但是实际上存在一定的问题。在 DDD 领域驱动设计中,领域层应该是核心层,但是传统的四层结构中基础层却处在核心位置,有点本末倒置的感觉。所以应该采用以领域层为核心的新的四层结构的方式优化原有的结构,实现对于基础层的解耦。

基础层:实际就是未微服务的其它层提供基础的通用技术支撑,如 Redis、MQ 以及数据库等,常见的就是数据库持久化可以放在这层当中。

用户接口层:负责向用户展示信息以及转化用户请求意图,在前后端分离的当下,可以实现在后端服务不变的情况下适配不同的用户前端的作用。

应用层:可以理解为实现领域对象以及多个聚合的服务编排以及组合,不应该有过多的业务逻辑。

领域层:领域层实际就是整个微服务的核心,在这其中涉及到领域对象的㛑状态变化以及领域规则。领域层主要包括实体、值对象、聚合根以及领域服务等。

当然实际上给还有其他的分层结构比如五层分层结构、六边形分层结构。


总结

本文主要围绕 DDD 领域驱动设计落地时间的三大过程进行了阐述,详细说明了战略设计阶段以及战术设计阶段的输入和输出。希望对于大家怎么去实施 DDD 领域驱动设计有一个方向的指引,后面的文章将继续深入阐述 DDD 具体落地实践的细节和一些建议。

发布于: 2021 年 11 月 29 日阅读数: 39
用户头像

真正的大师永远怀着一颗学徒的心 2018.09.18 加入

一线大厂高级开发工程师,专注Java后端以及分布式架构,在通往CTO的道路上不断前行

评论 (1 条评论)

发布
用户头像
不错的文章
2021 年 12 月 02 日 22:27
回复
没有更多了
DDD领域驱动设计落地实践系列:战略设计和战术设计