一篇尽量用人话把DDD讲明白的文章

王大爷 2022年05月02日 474次浏览

引言

在人类发展史上,当人们遇到棘手的问题时,一个惯用的手段,通常是将问题拆分

比如研究一个桃树由什么组成?

首先根据植物的器官,细分为树叶,桃花,桃子,树茎,树根,等。

于是问题变成了,树叶,桃花,桃子,各由什么组成?树叶是由叶片,叶柄,拖页组成,桃花由,花瓣,花粉,花柱等组成。

于是问题又变成了,叶柄是有什么组成的?

循环往复,直到现代科学探索到的细胞层面,细胞之间的细胞壁确定了单元的边界,也确定了研究的最小边界。

也许等到后面科技发展一定的程度,细胞壁仍然可以再分。

在这分析过程中,针对某些正对性的问题,还会衍生出专门分析一类问题的学科。

并且大家发现一个特征没有,《桃树由什么组成》这个问题分析工作是可以延续的,并且对未知留了足够的拓展空间。后人可以在前人的基础上继续拓展研究,而无需推翻重来。

其实,这就是一个DDD的过程,DDD其核心思想就是将问题域逐步分解,降低业务理解和系统实现的复杂度,同时让不同的人可以互不影响的完成整体目标。

只要按照这个思路去完成业务流程的拆分,基本就可以具备,延续性,拓展性 的基本特性。

从04年埃里克·埃文斯发表了《领域驱动设计》这本书,DDD一直处于不愠不火的状态,在软件开发领域一直都是雷声大,雨点小。

直到微服务大行其道的今天,一个很棘手的问题出现了,微服务如何拆分?以什么维度拆分?,DDD刚好很好的回答了这个问题。于是它开始大行其道。

当一个问题,当前我们无法预知它有多大的时候,首先是拆分问题,并为未来留足充足的拓展空间

image.png

一个DDD流程如何开始

现在,我问大家一个问题,给你一个业务,让你着手使用DDD去实现它。你的思路是什么?

你的第一任务,是先理清业务是什么。一个完整的业务有哪几个角色,角色之间如何交互,然后才能开展你的业务吧?

针对这个问题,无数大牛在落地DDD的过程中总结出了一个方法轮,叫做事件风暴。头脑风暴的一个变种,是一种流程化阶段化头脑风暴,每个阶段的成果用于下一阶段。

一个完整的事件风暴流程有如下几个阶段。

产品愿景->业务场景分析->领域建模->微服务拆分与设计

产品愿景。

产品愿景的主要目的是对产品顶层价值的设计,使产品目标用户、核心价值、差异化竞争点等信息达成一致,避免产品偏离方向。
参与人员有:领域专家、业务需求方、产品经理、项目经理和开发经理。

以用户中台为案例。

在建模之前,项目团队要思考这样两点:

用户中台到底能够做什么?

它的业务范围、目标用户、核心价值和愿景,与其它同类产品的差异和优势在哪里?

这个过程也是明确用户中台建设方向和统一团队思想的过程。参与者要对每一个点(下图最左侧列的内容)发表意见,用水笔写在贴纸上,贴在黄色贴纸的位置。这个过程会让参与者充分发表意见,最后会将发散的意见统一为通用语言,建立如下图的产品愿景墙。如果你的团队的产品愿景和目标已经很清晰了,那这个步骤你可以忽略。

image.png

这个步骤的目的是,让大家对产品雏形达成基本共识,把握大方向。

业务场景分析。

场景分析是从用户视角出发的,根据业务流程或用户旅程,采用用例和场景分析,探索领域中的典型场景,找出领域事件、实体和命令等领域对象,支撑领域建模。事件风暴参与者要尽可能地遍历所有业务细节,充分发表意见,不要遗漏业务要点。

场景分析的参与角色:领域专家、产品经理、需求分析人员、架构师、项目经理、开发经理和测试经理。

简言之这个过程,就是不断重复,谁(实体)让谁做了什么(命令),达成了什么效果(事件)的过程

最终形成一个,按照业务场景,命令,和事件,和额外信息的,集合图。

image.png

至此,你已经搜集出了业务中可能发生的所以事件,并且大致梳理出事件发生的先后关系。

领域建模

现在进入领域建模阶段,给上面搜集到的命令和事件找主人,即谁发出命令的,这里的”谁“就是我们需要找的实体。并且把一类人(实体)划到一块形成一个村落(聚合),找出一个族长(聚合根),族长掌管村落的大小事宜。

具体操作可分为三步。

  • 第一步:从命令和事件中提取产生这些行为的实体。用绿色贴纸表示实体。通过分析用户中台的命令和事件等行为数据,提取了产生这些行为的用户、账户、认证票据、系统、菜单、岗位和用户日志七个实体。

image.png

  • 第二步:根据聚合根的管理性质从七个实体中找出聚合根,比如,用户管理用户相关实体以及值对象,系统可以管理与系统相关的菜单等实体等,可以找出用户和系统等聚合根。然后根据业务依赖和业务内聚原则,将聚合根以及它关联的实体和值对象组合为聚合,比如系统和菜单实体可以组合为“系统功能”聚合。按照上述方法,用户中台就有了系统功能、岗位、用户信息、用户日志、账户和认证票据六个聚合。

  • 第三步:划定限界上下文,根据上下文语义将聚合归类。根据用户域的上下文语境,用户基本信息和用户日志信息这两个聚合共同构成用户信息域,分别管理用户基本信息、用户登录和操作日志。认证票据和账户这两个聚合共同构成认证域,分别实现不同方式的登录和认证。系统功能和岗位这两个聚合共同构成权限域,分别实现系统和菜单管理以及系统的岗位配置。根据业务边界,我们可以将用户中台划分为三个限界上下文:用户信息、认证和权限。
    image.png

最终形成一下表格,这就是我们事件风暴的最终成果。

image.png

微服务拆分与设计

微服务的拆分,以上面表格为重要参考依据拆分,但DDD最小粒度是可以支持到按照聚合划分微服务的,看公司业务需求,我们不能简单地将领域模型作为拆分微服务的唯一标准,它只能作为微服务拆分的一个重要依据。

微服务的设计还需要考虑服务的粒度、分层、边界划分、依赖关系和集成关系。除了考虑业务职责单一外,我们还需要考虑将敏态与稳态业务的分离、非功能性需求(如弹性伸缩要求、安全性等要求)、团队组织和沟通效率、软件包大小以及技术异构等非业务因素。

讲完了,这就是DDD的战略设计,DDD的核心。是不是很简单

落地

讲完战略设计,那一个DDD落地的代码结构应该是什么样的么?有哪几个组成部分呢?需要注意什么?

DDD最令人着迷的一点是,业务逻辑与底层编码可以同步衍化,咋一听好像不觉得有什么厉害的地方,举个例子,一般的需求从提出到落地的生命周期是什么样的?

需求方提出需求->产品经理给出设计prd->技术人员根据prd编码

这个过程中需求,prd,落地代码,除了那段代码实现了需求,其他任何关系都没有,什么意思?需求方,产品经理,开发,只关注自己的那部分。代码与需求脱节了,需求a,产生了代码片段a,需求b复用了一部分代码片段a同时产生了代码片段b,代码片段a与代码片段b交织在一起,没人关注,大家都只关心功能实现了没有,一个代码屎山慢慢产生了。

那代码如何做到可传承,避免屎山产生?DDD给出解决方案。

DDD的编码需严格按照战略设计结果的表格去落地的。

image.png

这是一个微服务设计实例的部分数据,表格中的这些名词术语就是项目团队在事件风暴过程中达成一致、可用于团队内部交流的通用语言。在这个表格里面我们可以看到,DDD 分析过程中所有的领域对象以及它们的属性都被记录下来了,除了 DDD 的领域对象,还记录了在微服务设计过程中领域对象所对应的代码对象,并将它们一一映射。

最终的代码结构如下。

image.png

这些目录的职能和代码形态是这样的。

Interfaces(用户接口层): 它主要存放用户接口层与前端交互、展现数据相关的代码。

前端应用通过这一层的接口,向应用服务获取展现所需的数据。这一层主要用来处理用户发送的 Restful 请求,解析用户输入的配置文件,并将数据传递给 Application 层。数据的组装、数据传输格式以及 Facade 接口等代码都会放在这一层目录里。

Application(应用层): 它主要存放应用层服务组合和编排相关的代码。

应用服务向下基于微服务内的领域服务或外部微服务的应用服务完成服务的编排和组合,向上为用户接口层提供各种应用数据展现支持服务。应用服务和事件等代码会放在这一层目录里。

Domain(领域层): 它主要存放领域层核心业务逻辑相关的代码。

领域层可以包含多个聚合代码包,它们共同实现领域模型的核心业务逻辑。聚合以及聚合内的实体、方法、领域服务和事件等代码会放在这一层目录里。

Infrastructure(基础层): 它主要存放基础资源服务相关的代码

为其它各层提供的通用技术能力、三方软件包、数据库服务、配置和基础资源服务的代码都会放在这一层目录里。