作者:faryrong,腾讯 CSIG 后台开发工程师
最近看了一本书《解构-领域驱动设计》,书中提出了领域驱动设计统一过程(DDDRUP),它指明了实践 DDD 的具体步骤,并很好地串联了各种概念、模式和思想。因此,我对书本内容做了梳理、简化,融入自己的理解,并结合之前阅读的书籍以及实践经验,最终形成这篇文章。希望可以帮助大伙理顺 DDD 的各种概念、模式和思想,降低上手 DDD 的门槛。
1.背景
领域驱动设计(DDD)由 Eric Evans 提出,并一经《领域驱动设计:软件核心复杂性应对之道》的发布,在软件行业中引起了不少的轰动。DDD 提供的一种新颖的,甚至有点“另类”的思维方式,它在告诉软件开发者“我们要用业务方案来解决业务问题,而不是技术方案解决业务问题”,有点魔法打败魔法的意思。DDD 虽然让人眼前一亮,但是所提倡的理念有点“违背直觉”(对开发人员而言),因此,在当时并没有流行开来。
后来,微服务架构的兴起,大伙惊奇地发现 DDD 是作为划分“微服务边界”的一把利器,并且 DDD 提及的很多设计理念与微服务架构十分契合,因此 DDD 逐渐被开发者们接受并流行起来。毫不夸张地说,了解和学习 DDD 可以算得上是如今软件行业从业者的一门必修课了。
但是!DDD 的学习曲线较为陡峭。作为一个小白,翻阅过很多相关的书籍、KM 文章和分享,但始终觉得未得要领、一知半解。原因有二:a) DDD 涉及的概念繁多,且不同概念的抽象层次不一样,如果我们直白地去理解,往往会感到疑惑,比如:子域和限界上下文都是用于将问题进行归类和收敛,他们的区别是什么?b)缺少过程指导,难以将概念有序的串联起来。作为方法论,DDD 给出了设计思想,核心原则以及常用工具,但是却缺少细致有序的方法步骤,导致难以上手实践。
幸运的是,最近看了一本书《解构-领域驱动设计》。这本书提出了领域驱动设计统一过程(DDDRUP),它指明了实践 DDD 的具体步骤,并很好地串联了各种概念、模式和思想。因此,我对书本内容做了梳理、简化,融入自己的理解,并结合之前阅读的书籍以及实践经验,最终形成这篇文章。希望可以帮助大伙理顺 DDD 的各种概念、模式和思想,降低上手 DDD 的门槛。
2.DDD 概要与实践感悟
经典必读书籍《领域驱动设计:软件核心复杂性应对之道》的书名包含了两个关键词:领域驱动和复杂性,分别代表了 DDD 的核心原则以及解决的问题。
2.1 复杂性
系统的复杂性往往并不在技术上,而是来自领域本身、用户的活动或业务服务。当这种领域复杂性在设计中没有得到解决时,基础技术的构思再好也是无济于事。而系统的复杂度体现在三个方面:规模、结构和变化。
规模:指的是系统所支持的功能点,以及功能点与功能点之间的的关系。DDD 通过子领域,限界上下文,聚合等模式对问题进行拆分和归类,不断收窄问题域,保证聚合边界内所解决的问题集合足够收敛和可控。
结构:指的是系统架构。系统架构是否分层;若分层,每层划分的职责边界是否清晰;架构的基本管理单元是什么,它决定了架构演进时的复杂度。DDD 通过分层架构,独立出领域层,且架构中的每层都有清晰的职责。整体架构的基本管理单元是聚合,它是一个完整的、自治的管理单元,当需要进行服务拆分时,可以直接以聚合作为基本单元进行拆分。
变化:指的是系统响应需求变化的能力。快速响应变化的有效手段是分离不易变逻辑和易变逻辑,"以不变应万变"。而通过分层架构独立的领域层正是不易变的逻辑。领域层是对领域知识的封装,其提供的领域服务具有经验性和前瞻性,是对领域内稳定的领域规则的表达。而领域层以外的应用层和基础设施层则是易变逻辑的封装。保证核心的独立和稳定,通过在调整应用层和基础设施层来实现快速响应需求变化。
2.2 领域驱动
领域驱动指的是以领域作为解决问题切入点,面对业务需求,先提炼出领域概念,并构建领域模型来表达业务问题,而构建过程中我们应该尽可能避免牵扯技术方案或技术细节。而编码实现更像是对领域模型的代码翻译,代码(变量名、方法名、类名等)中要求能够表达领域概念,让人见码明义。
结合实践经验,以下是本人对“领域驱动”的一些见解:
思维模式转变
实践 DDD 以前,我最常使用的是数据驱动设计。它的核心思路针对业务需求进行数据建模:根据业务需求提炼出类,然后通过 ORM 把类映射为表结构,并根据读写性能要求使用范式优化表与表之间的关联关系。数据驱动是从技术的维度解决业务问题,得出的数据模型是对业务需求的直接翻译,并没有蕴含稳定的领域知识/规则。一旦需求发生变化,数据模型就得发生变化,对应的库表的设计也需要进行调整。这种设计思维导致变化从需求穿透到了数据层,中间并没有稳定的,不易变的层级进行阻隔,最终导致系统响应变化的能力很差。
协同方式转变
过去由产品同学提出业务需求,研发同学根据业务需求的 tapd 进行技术方案设计,并编程实现。
这种协同方式的弊端在于:无法形成能够消除认知差异的模型。产品同学从业务角度提出用户需求,这些需求可能是易变的、定制化的,而研发同学在缺少行业经验的情况下,往往会选择直译,即根据需求直接转换为数据模型。而研发同学从技术实现角度设计技术方案,其中涉及很多的技术细节,产品同学无法从中判断是否与自己提出的业务诉求和产品规划相一致,最终形成认知差异。且认知差异会随着迭代不断被放大,最后系统变成一个大泥球。
![8fd2d1a27c0b0f0cc24b9591dd52f6d2.png](https://i-blog.csdnimg.cn/blog_migrate/e60e4b377620deb6383e5a0e279de9c9.png)
DDD 通过解锁新角色”领域专家"以及模型驱动设计,有效地降低产品和研发的认知差异。领域专家是具有丰富行业经验和领域知识储备的人,他们能够在易变的、定制化的需求中提炼出清晰的边界,稳定的、可复用的领域概念和业务规则,并携手产品和研发共同构建出领域模型。领域模型是对业务需求的知识表达形式,它不涉及具体的技术细节(但能够指导研发同学进行编程实现),因此消除了产品和研发在需求认知上的鸿沟。而模型驱动设计则要求领域模型能够关联业务需求和编码实现,模型的变更意味着需求变更和代码变更,协作围绕模型为中心。
![36df53185eda4823a3f85f26dc646bfe.png](https://i-blog.csdnimg.cn/blog_migrate/38c3d65a2536a3f3fd49283c7530f990.png)
精炼循环
精炼循环指的是在统一语言,提炼领域概念,明确边界,构建模型,绑定实现过程中,这些环节相互影响和反馈,在不断的迭代试错-调整以最终沉淀出稳定的、深层次的模型的过程。比如,我们在提炼领域概念的时候会觉得统一语言定义不合理/有歧义,此时我们就会调整统一语言的定义,并重新进行提炼领域概念。通过精炼循环,我们逐步形成稳定的领域模型。在 DDD 中,让领域专家来主导概念提炼、边界划分等宏观设计,原因就在于领域专家的经验和行业洞见来源于过去已经迭代的无数个精炼循环,因此由这些宏观设计推导出来的领域模型,往往都是非常稳定的。
精炼循环的核心是循环,它避免知识只朝单一方向流动,最终因各环节上的认知差异,最终导致模型无法在产品、领域专家和研发中达成一致、模型与实现割裂。
2.3 怎么才算 DDD?
我早期实践 DDD 的时候,认为代码分层遵循四层架构就是 DDD,抑或分离接口和实现,实现下沉至基础设施层就是 DDD,实则不然。结合上述内容,目前个人认为只要满足以下条件即为实践 DDD:
构建出产品、领域专家和研发同学认知一致且便于交流的模型,并且模型与实现紧密绑定;
模型逐步演进,反复消化和精炼;
模型蕴含领域知识,足够稳定。
3.问题空间&解空间
3.1 问题空间&解空间
问题空间和解空间并非 DDD 特有的概念,而是人们为了区分真实世界和理念世界而提出的概念。问题空间表示的是真实世界,是具体的问题、用户的诉求,而解空间则是针对问题空间求解后构建的理念世界,其中包括了解决方案、模型等。
DDD 提出的战略设计覆盖了问题空间和解空间,而战术设计则聚焦在解空间上。明确 DDD 中的概念是作用于问题空间还是解空间,更有助于我们理解它们。
3.2 示例-学生管理系统的问题空间
学生管理系统(Student Management System,下文简称 SMS)作为 DDDRUP 的讲解示例,以下为其问题空间的描述。
学校需要构建一个学生管理系统(Student Management System, SMS)。
通过这个管理系统,学生可以进行选课,查询成绩,查询绩点。
而老师则可以通过这个系统录入授课课程的成绩。录入的分数会由系统自动换算为绩点,规则如下:若分数>= 90,绩点为4.0;90>= 分数> 80,绩点为3.0;80 >= 分数 > 70,绩点为2.0;70 >= 分数 >= 60,绩点为1.0;成绩< 60,则没有绩点,并邮件通知教务员,由教务员联系学生商榷重修事宜。
成绩录入后的一周内,若出现录入成绩错误的情况,老师可提交修改申请,由教务员审核后即可完成修改。审核完成后系统会通过邮件告知老师审核结果。一周后成绩将锁定,不予修改。成绩锁定后,次日系统会自动计算各年级、各班的学生的总绩点(总绩点由各门课程的学分与其绩点进行加权平均后所得)。
而教务员则可以通过该系统发布可以选修的课程。同时,教务员能够查看到各年级,各班的学生的总绩点排名。
4.领域驱动设计统一过程(DDDRUP)
虽然领域驱动设计划分了战略设计和战术设计,也提供了诸多模式和工具,但却没有一个统一过程去规范这两个阶段需要执行的活动、交付的工件以及阶段里程碑,甚至没有清晰定义这两个阶段如何衔接、它们之间执行的工作流到底是怎么样的。
而《解构-领域驱动设计》提出的 DDDRUP 给出了更细致的步骤、步骤与步骤之间的衔接,以及明确的阶段里程碑,最重要的是 DDDRUP 可以串联 DDD 的所有概念和模式,非常便于初学者做知识梳理和上手实践。下文我会依照 DDDRUP 的步骤流程进行讲述,而非战略设计+战术设计的思路。(DDDRUP 各步骤与战略&战术设计的关系见下表)。
![47566ef65d633bdd5035cd238fb5092b.png](https://i-blog.csdnimg.cn/blog_migrate/e90e742ee514b6c4769e3d708b55432c.png)
5.全局分析阶段
全局分析阶段对问题空间进行的梳理和分析,形成统一语言(ubiquitous language), 获取问题空间的价值需求以及业务需求。
5.1 形成统一语言
统一语言:蕴含领域知识的、团队内统一的领域术语。产品、领域专家以及开发人员掌握的领域知识存在差异,往往导致对同一个事物使用不同的术语。比如,商品的价格(Price)和商品的金额(Amount),它们本质是同一个东西,但是却有不同的术语表示。
统一语言会参与 DDDRUP 的全流程,且会在精炼循环过程中不断进行调整,以反映出更合适、更深层次的领域知识。
根据业务需求形成统一语言,有助于团队对事物的认知达成一致。统一语言可以通过词汇表的形式展示,其中词汇表最好还要包含术语对应的英文描述,便于研发同学在代码层面表达统一语言。示例-SMS 的统一语言词汇表如下。
![9aad8c13ac6073ff62a9cf0adc34d65f.png](https://i-blog.csdnimg.cn/blog_migrate/1f47625a66eff9415998ef1473454835.png)
5.2 价值需求分析
价值需求分析主要做的三个工作是:
识别利益相关者。利益相关者指的是与目标系统存在利益关系的人、团队或组织, 可以简单理解为目标系统的用户,或与目标系统有直接交互的人、团队或组织。
明确系统愿景。阐明目标系统要做什么,以及为何要做。
确定系统范围。确定系统问题空间的边界,明确系统什么该做,什么不该做。结合目标系统当前状态和未来状态进行判断。当前状态指的是系统的可用资源,包括业务资源、人力资源,资金资源等;而未来的状态则由业务目标、组织的战略规划和产品规划共同构成。
并非任何系统都 DDD,DDD 的核心是解决领域复杂性,若系统逻辑简单,功能不多,引入 DDD 则会得不偿失。而在进行价值需求分析后,我们便能判断是否需要通过 DDD 驱动系统的设计。
5.3 业务需求分析
5.3.1 业务流程、业务场景、业务服务和业务规则
使用业务流程、业务场景、业务服务和业务规则来表示业务需求。
业务流程:表示的是一个完整的、端对端的服务过程。
业务场景:按阶段性的业务目标划分业务流程,就可以获得业务场景。在示例-SMS 中,老师修改成绩就分为了老师“提交申请单”,以及教务员“同意申请单”两个场景。
业务服务:角色主动向目标系统发起服务请求完成一次完整的功能交互,以实现业务目标。角色可以用户、策略(定时任务)或者其他系统,完整则强调的是业务服务的执行序列的所有步骤都应该是连续且不可中断的。业务服务是业务需求分析最核心,也是最基础的单元,而业务流程和业务场景是为了更好地分析出业务服