今日内容
聊完了原则,我们来看看具体的实践。今天我们就来讲如今比较推崇的DDD领域驱动设计。
由于DDD的内容多且细,本文会尝试以精简且容易理解的方式讲述DDD的重点概念及用法,目的是帮你对DDD形成一个体感上的认知。
本文篇幅略长,希望你可以耐心阅读。如果时间紧张,你可以先做收藏。
01什么是DDD
DDD的全称为domain-driven design,也就是领域驱动设计。
DDD诞生于2003年,当时并没有受到业界特别的关注,主要原因是当时的软件开发方法不注重业务模型的设计和建模,而更加偏向功能导向和技术导向。随着微服务、云计算等技术的兴起,对于复杂业务的切分和组织变得更为关键,这就需要更优秀的业务架构设计,而DDD则提供了一种有效的业务架构设计方法,逐渐受到大家的关注和认可。
虽然这些年DDD大行其道,但大家对DDD的评价却褒贬不一。一方面,DDD可以帮助构建业务规则复杂的系统,并使其保持良好的扩展性。但另一方面,DDD整体的理论比较抽象,概念偏多,这使得很多人运用的时候往往不得其法,浪费了很多时间。
所以,系统设计是否使用DDD需要一事一议。但不可否认,DDD中蕴含的设计思想是非常值得我们学习和借鉴的。
02战略设计与战术设计
为了更好地介绍DDD,我们先构建一个虚拟的业务场景。我们后面所有的内容都会围绕这个业务场景展开。
我们假设有一个小型的电商网站,卖家可以在上面出售商品,买家选择商品并购买,购买后网站把款项结算给卖家。
没错,这就是互联网最典型的应用场景。
在DDD中,可以将整体的设计分为【战略设计】和【战术设计】。两者在设计的侧重点上不同,我们将分别展开说明。在整个说明过程中,重点的概念也会逐一登场。
01战略设计
战略设计的目的是:帮助开发人员和业务人员建立共同理解的业务模型,划分业务边界。
这里有两个重点:【1】对齐技术人员和业务人员对于业务流程、业务规则、业务关键概念的认知,这样的好处不言而喻。【2】划分业务边界可以指导工作内容拆分。在微服务背景下,战略设计的产出可以直接指导微服务划分。
战略设计的主要产出内容如下:
我们来介绍下其中的几个概念:
【领域】是指一个具有独立性和自治性的问题空间。听着比较抽象,实际上没那么复杂。就拿我们的虚拟场景来说,领域就是“电商”。对于【领域】来说,最重要的是两个概念:范围 + 知识体系。“范围”告诉我们,领域是有边界的。“知识体系”告诉我们,领域内的概念和规则是有专业性的且相互关联的。
【子域】顾名思义,就是将领域进行划分,变成一块块更小的“领域”。每个“小领域”也必须具有“范围+知识体系”的特征。子域划分使用了分而治之的经典思想。在我们的虚拟场景中就会包括“商品域”、“支付域”、“结算域”等等。
【限界上下文】是针对子域更小的划分,其中包含了多个具体的领域模型。限界上下文的作用是将业务紧密关联的领域模型放在一起,并且提供统一的语境(各种概念在限界上下文中有统一的含义)。例如虚拟场景中,一个商品上单时我们将用户输入的卖价称之为原价,售卖时则将用户不使用任何优惠活动的价格称之为原价。
“限界上下文”对微服务拆分有重要的指导意义。
【领域模型】在“事件风暴”(我们马上会讲到)中找到的和业务相关的各种模型。例如各种单据模型、角色模型、商品模型、权限模型等等。其中,很多模型之间有非常紧密的关系。这四个概念我们是从大到小来说的,但在做战略设计的时候,其实是反过来的,这个我们后面就会看到。
02战术设计
战术设计是指:基于战略设计的结果,进一步分析领域模型并补充细节,接着将领域模型转换为具体的代码。其中主要包括【领域服务的识别】和【代码层次的划分】。我们在后面的小结马上可以看到,在这里我们先对一些关键的概念做一些了解。
【实体】指在业务领域中具有生命周期和业务行为并能产生变化的对象,如订单、客户等。
【值对象】是指在业务领域中用来描述某些特定属性或属性组合的对象,通常是不可变的,如地址、手机号等。
【聚合与聚合根】是用来组织“实体”和“值对象”的一种模式。聚合定义了一个业务逻辑上的整体,其中包含多个“实体”和“值对象”。聚合根是聚合中的一个实体,负责维护聚合内的完整性和一致性。
【领域服务】是指处理业务逻辑的函数,没有“实体”或“值对象”,它负责处理需要多个对象协作才能完成的复杂业务场景。
【领域事件】是指在业务领域中发生的某些重要事件,它们会对业务流程产生影响。同时,事件还将通知所有对此事件感兴趣的相关方,比如其他领域、系统或模块。
我相信,直接看这些概念会觉得生涩难懂。没关系,你只需要对这些词有点印象,下面我们结合虚拟场景来做设计时,你会再逐一看到他们。
03战略设计之领域建模
如上所述,我们首先要进行战略设计。战略设计的目的是:帮助开发人员和业务人员建立共同理解的业务模型,划分业务边界。
01事件风暴
业务模型不会直接跃然纸上,我们需要通过一些方法,将大家脑中的业务规则、业务概念、业务流程以及之间的各种关系呈现出来。更重要的是,大家需要对齐这些业务相关内容的认知。【事件风暴】就是最常用的方式。
简单地说,事件风暴就是把相关的同学拉到一起,按照一套特定的组织逻辑来分析业务过程,识别业务中的关键概念。
这套特定的组织逻辑包括了“事件”、“参与方”、“命令”、“业务规则”等几个重要概念,以及他们之间的逻辑关系。这些内容指导我们开展事件风暴。
《图片来源于virtualDDD》
【参与方Actor】:参与业务流程的角色,可以是人也可以是系统。他可以出发命令,同时可以在视图上看到反馈。
【命令Command】:参与方发起的行为。命令可以触发系统行为,也可以称之为调用系统。
【外部系统ExternalSystem】:不需要知道细节的服务提供方。提供服务后会产生事件。
【领域事件DomainEvent】:业务过程中发生的重要事件。事件会激活策略。
【策略Policy】:策略负责响应领域事件,可以触发新的行为。
【视图QueryModel】:视图用来与参与方打交道,供参与方继续触发命令。
【热点Hotspot】:一些需要关注的问题,可能是业务问题(用户是否可以重复购买同一个商品),也可能是技术问题(性能瓶颈)。需要进一步讨论。
如果觉得生涩没有关系,下面我们也还会看到这些。这里你需要体会的是:分析业务有如上这些要素,他们之间相互依赖和影响。正是基于这种影响,我们才能够分析出来完整的业务流程,继而形成领域模型。
下面基于我们的虚拟场景,看看如何来做【事件风暴】。
【step1:准备工作】
工具准备。包括:一个方便大家来回走动的会议室、各种颜色的贴纸(不同颜色贴纸的作用与上面介绍的事件风暴组织图一致)、笔、一块大的黑板或者白板。
人员准备。包括:业务专家、产品、设计师、架构师等。这些人员或与系统建设相关、或了解业务规则概念、或与产品建设相关。
【step2:开场】
事件风暴过程中需要一个专职的主持人。主持人最重要的就是负责“一个接一个问问题”,驱动在事件风暴过程中产出领域模型。
开场时,主持人需要交代事件风暴会议的背景、目标、内容、要求、贴纸的用法等。
【step3:梳理事件】
之所以从事件开始,是因为事件代表某个行为的结果,是业务的重点。从事件可以正推或者反推整个业务流程,并呈现其中的各种业务概念。
例如在我们的虚拟场景中,卖家上了商品后就会有“商品已上架”事件。我们可以沿着这个事件继续思考,这个事件的前后还会有哪些事件?
“商品已上架”之前,商品肯定被创建了,所以肯定会有“商品已创建”事件。“商品已创建”后就上架了吗?一般还会有审核环节,所以会有“商品已审核”事件......
“商品已上架”之后,用户就可以进行购买。所以就会有“用户已支付”事件。“用户已支付”事件前肯定还会先创建订单,所以有“订单已创建”事件......
按照这个思路,我们就得到了如下这样的视图:
【step4:识别其他要素】
在梳理完领域事件之后,我们就可以以这些事件为线索,把其他信息给补上。
补充命令。命令就是行为,一定是有具体的行为触发了上面这些领域事件。例如“商品已创建”肯定是由“创建商品”这个命令触发的。“订单已创建”则是由于用户选择了商品后触发的等等。
补充参与方。上面的这些命令一定是由参与方触发的,可以标识出来。
补充策略。策略是对事件的响应。例如收到“商品已审核”事件,就会有策略来驱动将商品放上货架。收到“用户已支付”事件后就会驱动生成物流单。策略更广义地说就是业务规则。
其他还有补充视图、热点问题、第三方系统,我们不一一论述了,直接看下补充后的结果:
上图中:橙色是事件、蓝色是命令、粉色是策略、绿色是界面、黄色是参与方、紫色是三方系统。
当然,一个真正的电商网站比上述分析的内容要复杂得多。为了方便叙述,我们就挑重点的一些来介绍了。
02分析领域模型
做完了事件风暴,下面就是根据事件风暴的结果来进行建模了。我们可以从事件、命令、策略中提取名词,这些名词往往就是【领域实体】或者【值对象】。下图就是我们分析并提取的内容(我们在事件风暴基础上做了一些补充):
接着,我们需要对这些“实体”及“值对象”做聚合。还记得上面聚合的定义吗?忘了?没关系,这里我再贴一下。
聚合定义了一个业务逻辑上的整体,其中包含多个“实体”和“值对象”。而聚合这些“实体”和“值对象”的实体称之为聚合根。
下图就是我们聚合后的结果,其中绿色的就是聚合根。
03划分限界上下文
在得到聚合和聚合根以后,我们就需要来划分限界上下文。限界上下文指导我们拆分服务,所以限界上下文圈定的内容就会放在一个系统中建设。
限界上下文划分的依据是:在一个限界上下文中有一个完整的业务子流程,并且提供了统一的语境。根据这个指导方针,我们将上面的聚合做如下划分:
上图可以看到,在划定限界上下文时,我们也同时标出了各种子域。
04战术设计之代码实现
在完成限界上下文的划定后,针对每一个限界上下文就进入了战术设计阶段。这个阶段依然需要使用到我们上面分析获得的【事件风暴】结果。
我们仅以【商品限界上下文】为例来展开论述。
【step1:分析领域服务】
我们在战略设计中根据事件、命令、策略中的名词提取了领域模型中的实体和值对象。
“实体”和“值对象”偏重于表现数据,也就是这些实体包含了哪些信息。虽说实体本身也提供了业务规则相关的能力,但能力比较琐碎。我们往往需要将多个实体聚合在一起对外提供能力,而这样的能力我们就称之为“领域服务”。而将“领域服务”编排起来提供的能力就表现为了系统对外的能力。
领域服务主要来自于“事件风暴”中的命令。我们可以看到,命令有:创建商品、编辑商品、提交审核、审核通过、商品上架。
【step2:划分代码层次】
接下来我们来讲DDD经典的分层模型。
如上图所示,在DDD分层设计中,主要分为四个层次:
【接口层】:对出入参仅做格式上的校验,不能涉及“例如用户是否在黑名单中”这样的校验。
【服务层】:负责编排流程、处理rpc请求、控制同异步。不能涉及领域概念。
【领域层】:针对领域规则来实现具体的能力,不能跨领域。
【基础层】:这一层严格意义上来说并不处于最下面一层。在上面三层中,所有的外部依赖(DB、缓存、中间件、服务调用)都使用接口的方式来避免和具体技术耦合。而基础层提供了这些接口的具体实现。
很多文章都写到:经典的DDD分层框架中,所有和外围应用打交道都应该是应用层的逻辑。所以,多个系统间的交互是如下这样:
不过根据实际经验来看,很多时候领域层的领域服务往往也会直接做RPC调用和异步事件交互。
事实上,DDD的分层及交互标准可以根据实际情况做一些修改,只要不破坏一些关键原则(例如依赖关系不能倒置、除了基础层不能有对外部依赖的具体实现)。切记不要为了DDD而DDD。
【step3:具体代码目录】
结合上面提到的分层,我们最后来看下具体的代码目录结构是怎样的。
为了便于解释各个目录的作用,我就直接把说明标注在图中了。
到这里,我们就把DDD领域设计相关的内容都讲完了。
今日小结
今天我们介绍了近些年炙手可热的DDD领域驱动设计。结合我们给出的简易电商场景,给大家介绍了DDD的关键概念以及如何做从战略到战术的各种设计。