最近接触到了 DDD(Domain-Driven Design,领域驱动设计)的思想,找了一些资料进行学习,涉及到很多术语,在此做个记录。

首先要说的是:DDD 是 Eric Evans 在 2003 年出版的《领域驱动设计:软件核心复杂性应对之道》(Domain-Driven Design: Tackling Complexity in the Heart of Software)提出的一个概念,是一种方法论(思想)。类似于之前接触到的 MVC 的分层思想,DDD 不是个架构,但是是有一些架构是基于 DDD 思想实现的。

接下来就和大家分享一些自己最近接触到的有关 DDD 的名词以及自己对他们的理解。

领域

首先说下自己对领域的理解,领域其实就是一个范围或者区域。举个例子吧:比如银行行业、保险行业、航空行业之类的。

DDD 会在研究和解决业务问题时,按照一定标准将业务领域进行划分,当领域被细分到一定程度后,问题会被现在在特定的范围(边界)内,然后解决此范围内的问题。

DDD 中的领域就是解决上述所说范围(边界)内要解决的业务问题域。

子域

我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围。

子域可以根据自身的重要性和功能属性来划分为三类子域:核心域、通用域、支撑域。

核心域

决定产品和公司核心竞争力的子域是核心域,它是业务成功的主要因素和公司的核心竞争力。

通用域

没有太多个性化诉求,同时被多个子域使用的通用功能子域是通用域。

支撑域

功能子域是必需的,但既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域,它就是支撑域。

以研究一个桃树为例:

  1. 确定研究对象,即研究领域:桃树。
  2. 对研究对象进行细分。将桃树细分为器官,器官又可分为营养器官和生殖器官;营养器官又包括根、茎、叶,生殖器官有花、果实、种子。
  3. 对器官进行细分。将器官细分为组织,比如,叶子器官可细分为保护组织、营养组织和输导组织等。这个过程就是DDD将子域进一步细分为多个子域的过程。
  4. 对组织进行细分。将组织细分为细胞,细胞成为我们研究的最小单元。细胞之间的细胞壁确定了单元的边界,也确定了研究的最小边界。

模型

然后在上面说到的范围/领域内建立模型,然后用代码实现该模型,解决相应的业务问题。

那什么是模型呢?模型其实就是咱们对领域思考过程汇总的地方。比如咱们可以用图、用例、文字将模型展示出来,然后用来交流。

领域和模型的关系可以理解为:领域是模型的抽象,模型是领域的体现。

通用语言

怎么样建立一个模型呢?

由于对领域的理解不一样,开发和产品之间沟通有时候就挺麻烦的。那在从领域中建立模型的时候,那么多的参与者(领域专家、产品经理、项目经理、架构师、测试经理等)对某个领域有不同理解怎么办?

所以咱们需要一个通用语言,所谓通用语言就是在 事件风暴(可以简单理解为头脑风暴)过程中,通过团队交流达成共识的,能够简单、清晰、准确描述业务涵义和规则的语言就是通用语言。

有了通用语言,可以解决交流障碍的问题,使得各个专业的人员和开发人员能够协同合作,确保业务需求的正确表达与实现。

另外,通用语言还包含术语和用例场景,也能够直接反映在代码中。通用语言中的名词可以给领域对象命名,如商品、订单等,对应实体对象;而动词则表示一个动作或事件,如商品已下单、订单已付款等,对应领域事件或者命令。

限界上下文

限界上下文用来确定语义所在的领域边界,也确定了微服务的设计和拆分方向,是微服务设计和拆分的主要依据。理论上一个限界上下文可以设计为一个微服务。

可以将限界上下文拆解为两个单词,限界和上下文:

  • 限界:就是领域的边界
  • 上下文:语义环境。

举个例子吧:一天一个孩子问自己母亲自己该穿几件衣服,母亲回复到「能穿多少穿多少」。这个时候,咋理解这个「能穿多少穿多少」,是多穿还是少穿?没有一个具体的环境还真是难说,要是是炎炎夏日就该少穿,如果是寒冬腊月就该多穿。这个时候「炎炎夏日」和「寒冬腊月」就是咱们说的环境,即上下文。

再来个实际点的例子:

Java开源的DDD领域驱动设计项目_DDD


在上图的中,保险模型是被拆分了投保、理赔、保单、管理、理赔四个子域。其中理赔又被拆分成了报案、勘察、定损和其它四个子子域。其实这四个子子域就是四个限界上下文。当然投保也算是一个限界上下文。

说下自己的理解:

  1. 领域对应着限界上下文;
  2. 团队在限界上下文中用通用语言交流。

限界上下文本质上也是子域,限界上下文是在明确的子域内,用事件风暴划分出来的。它体现的是一种详细的设计过程。这个过程设计出了领域模型,明确了领域对象以及领域对象的依赖等关系,有了领域模型,你就可以直接进行微服务设计了。——《DDD实战课:基于DDD的微服务拆分与设计》

实体

实体就是一个独立的事物。每个实体拥有一个唯一标识符,可以将它的个体性和其他所有类型相同或者不同的实体区分开。

唯一标识符:一般是对象的一个属性或者属性的集合,一个专门为了保存和表现标识符而创建的属性,也或是一种行为

我们可以对实体进行修改,修改后的实体状态和之前可能不一样。由于它们(修改前的实体和修改后的实体)拥有相同的标识符,所以它们仍然是一个实体。

在 DDD 中,实体类通常采用「充血模型」。

特点:
有 ID 标识,通过 ID 判断相等性,ID 在聚合内唯一即可。状态可变,它依附于聚合根,其生命周期由聚合根管理。实体一般会持久化,但与数据库持久化对象不一定是一对一的关系。实体可以引用聚合内的聚合根、实体和值对象。

值对象

值对象是对一个不变的概念整体所建立的模型,用来描述领域的特殊方面、没有标识符的、可以作为不变量的、可以被另一个值对象替换的一个对象。

本质上值对象就是个集合。包含若干个用于描述目的、具有整体概念和不可修改的属性。使用值对象可以保证属性归类清晰和概念完整,避免属性零碎。

咱们来举个例子:

Java开源的DDD领域驱动设计项目_DDD_02

public class Person{
	public String lD;//值对象人员唯一主键
	public String name;//单一属性值对象
	public int age;//单一属性值对象
	public boolean gender;//单一属性值对象
	public Address address;//属性集值对象,被实体引用
	∥省去方法
}
public class Address{//值对象无主键ID 
	public String province;//值对象
	public String city;//值对象
	public String county;//值对象
	public String street;//值对象
	∥省去方法
}

其中:Person 实体包含 name、age、gender、province、city、county、street 这几个属性,这样看起来地址相关的属性有点零碎,所以我们将「province、city、county、street」这几个属性抽出来构成一个 address 的对象集合,此时这个 address 对象就是一个值对象。

作为值对象的 address,是 province、city、county、street 的集合,描述了 person 这个实体的其他方面,address 这个值对象中也没有唯一标识符,可以作为不变量,也可以被另一个值对象替换。

之前咱们持久化到数据库有两种方案:

  1. 将实体的属性值和值对象的属性值都保存在同一个数据库实体表中;
  2. 创建实体和值对象两个实体,对应数据库中两张表;

用了 DDD 之后:可以在领域建模时将部分对象设计为值对象,在数据建模时将值对象嵌入实体,减少实体表的数量。

特点:
无 ID,不可变,无生命周期,用完即扔。值对象之间通过属性值判断相等性。它的核心本质是值,是一组概念完整的属性组成的集合,用于描述实体的状态和特征。值对象尽量只引用值对象。

聚合与聚合根

聚合

通过事件风暴根据业务操作和行为确定了实体和值对象之后,将相关的实体和值对象进行组合就构成了聚合。根据业务将多个聚合划分到同一个限界上下文中。

在限界上下文和实体之间增加聚合能够让各实体和值对象在实现共同的业务逻辑时保证数据一致性。打个比方:社会是一个个的个人构成的,随着社会发展与要求,出现了部门、社区、机构等组织相关的概念。个人成为了组织中的一员,大家可以协作发挥更大的力量。

实体和值对象就相当于上述例子中的个人,而让实体和值对象协作的就是聚合。

特点:
高内聚、低耦合,它是领域模型中最底层的边界,可以作为拆分微服务的最小单位,但我不建议你对微服务过度拆分。但在对性能有极致要求的场景中,聚合可以独立作为一个微服务,以满足版本的高频发布和极致的弹性伸缩能力。

聚合根

每个聚合中都有一个根,这个根称为聚合根,也被称为根实体。如果把聚合比喻成组织的话,聚合根就是组织的管理者。

聚合根是一个实体,而且他是外部可以访问的唯一的一个对象。聚合根可以保持对任意聚合对象的引用,并且其他对象可以持有任意其他的对象,但是一个外部的对象只能持有根对象的引用。

特点:
聚合根是实体,有实体的特点,具有全局唯一标识,有独立的生命周期。一个聚合只有一个聚合根,聚合根在聚合内对实体和值对象采用直接对象引用的方式进行组织和协调,聚合根与聚合根之间通过 ID 关联的方式实现聚合之间的协同。

例子

以保险的投保业务场景为例:

Java开源的DDD领域驱动设计项目_领域驱动设计_03

待添加:

  1. 领域事件
  2. 领域服务
  3. 工厂
  4. 资源库

参考

《领域驱动设计精粹》
《实现领域驱动设计》
《代码精进之路:从码农到工匠》
《DDD实战课:基于DDD的微服务拆分与设计》