对DDD的总结

前言

第一次听到DDD,还是通过技术群内部大佬们的闲聊得知。本着求知的心态,在网上简单看了一下DDD相关的知识。晦涩的术语,抽象的描述,看了半天不禁感叹,大佬们之间的聊天话题,果然不是我等咸鱼可以企及的。看着大佬们在群里谈笑风生,羡慕他们广阔的知识面之余,我也对DDD产生了兴趣,接着便是加大力度在网上搜索相关的文章、视频。文章很多,视频很少,能让我这种咸鱼看完之后能理解的,更是少之又少。不过也不是毫无收获,总的看下来,对DDD也是有了一定的理解。接下来我将通过我所看到的文章、视频、以及我自身对DDD的理解,以我能理解的方式,对DDD中比较常见的概念、术语做一个总结,除了巩固一下自身对DDD的认识,也希望能给看到这篇文章的同学,提供学习DDD方面的参考。在这里给大家推荐<<阿里技术专家详解DDD系列>>

什么是DDD

DDD即domain-driven design(领域驱动设计),它是一种架构思想,而非实际使用的框架。在传统的开发模式中,经常是接到业务后,我们就开始根据业务涉及的场景、交互方式、页面元素等信息,来构建我们的表模型、画流程图,最终通过对数据的操作来实现我们的功能,这就是典型的数据驱动设计。而DDD思想主张从业务角度出发,对业务模型进行分析,通过构建我们的领域模型,由领域来驱动设计。

DDD术语

领域驱动设计主要分为了两个过程:战略设计、战术设计。战略设计主要是通过系统整体,来帮助我们划分领域以及处理领域间的关系;而战术设计则从设计实现的层面教会我们如何具体地实施DDD,更偏向于开发。

战略设计

DDD的战略设计主要包括领域/子域、通用语言、限界上下文等概念。

领域

百度百科对领域的解释:

具体指一种特定的范围或区域,一种专门活动或事业的范围、部类或部门。

由此可以看出:领域就是来确定范围的,范围即边界,DDD 的领域就是要解决的业务范围。例如在电商系统中,可能有“商品领域”,“订单领域”,“仓储领域”,“物流领域”。而领域模型指的就是业务的载体,例如,商品,订单。

子域

既然领域是用来限定业务边界和范围的,那么就会有大小之分,领域越大,业务范围就越大,反之则相反。如果一个领域过大,还可以把领域可以进一步划分为子领域。划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围。根据重要性与功能将可以领域分为大致三类的多个子域,分别是核心域、支撑子域和通用子域。

  • 核心域:决定产品和公司核心竞争力的子域,是业务成功的主要因素和公司的核心竞争力。
  • 支撑子域:既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域,但又是必需的支撑域。支撑域具有企业特性,但不具通用性,例如数据代码类的数据字典等系统。
  • 通用子域:没有太多个性化需求,同时被多个子域使用的通用功能子域。比如认证、权限等,无企业特点限制,无需太多定制化。

例如在一个商城系统中,用户下单、支付、退款是商城中最基本也是最核心的功能,也就是核心域;会员、意见反馈等不影响主要功能的就是支撑子领域;认证、权限、消息等基础服务,可通用且与业务关系不大的功能就是通用子域。

Bounded Context(限界上下文)

每个限界上下文专注于解决某个特定的子域的问题。 每个子域都对应一个明确的问题,提供独立的价值,所以每个子域都相对独立。 子域及其对应的限界上下文中的模型会因为其要解决的问题变化而变化,不会因为其他子域的变化而变化,即低耦合;当一个子域发生变化时,只需要修改其对应限界上下文中的模型,不需要变动其他子域的模型,即高内聚。

我们对Context(上下文)的概念应该并不陌生,平时开发中我们经常会将“上下文”理解为“作用域”,或者说是一个“范围”。例如我们的Spring Context、Session Context。我们通过当前所处的上下文环境,来判断某些状态、行为的作用或代表的含义。DDD中的限界上下文也是同一个道理,它表示领域的边界。在上下文覆盖的范围,领域内的状态或行为有自己的含义。

例如:我购买了一套复式的毛坯房,准备自己装修设计,一楼朝南部分划分一大块区域做为客厅,旁边划分一小块区域做为厨房,朝北的做为厕所。二楼划分了两块,大的一块做为卧室、小的做为阳台。我通过房子的朝向、风口,来划分客厅、阳台、厕所,其实就是通过分析当前的场景,来划分领域的动作。区域覆盖的范围,代表不同的领域,它的边界,就是我们的所说的“限界上下文”,范围也被赋予了含义。

Context Map(上下文映射)

上下文映射,表示界限上下文的一种映射关系,它可以让我们从宏观上看到每个上下文之间的联系,一旦发现某个限界上下文与过多的其它限界上下文具有联系,我们就可以对限界上下文进行细分了。前面我们将房子根据场景分配了几个大小不一区域,从客厅去我们的卧室,需要通过连接一楼到二楼的楼梯,去厨房我们需要经过走廊,从一个领域到达另一个领域,连接楼层的楼梯与连接客厅厨房的走廊,其实就对应着领域上下文之间的映射。

Ubiquitous Lanuage(上下文通用语言)

限界上下文需要通过上下文通用语言来理解领域模型代表的动作与含义。还是用前面建房子来举例,我分配完各自的区域后,需要在各自的区域装上门,将门装到厕所,它就是厕门;装在客厅,它就是大门。同样的门,被安装在不同的区域(领域上下文),表达的意思也不同,这就是我们所说的“上下文通用语言”。在汉语中,我们可以将界限上下文理解为“语境”,上下文通用语言理解为“语意”。在不同的“语境”,“语意”的含义也不同。

战术设计

与战略设计不同,战术设计更关注代码层面的实现,通过面向对象的思维设计类的属性与方法,将DDD落地到实际的开发中。

基本概念

Entity(实体)

通俗来讲,就是由方法和属性实现业务的类,与传统的VO(View Object)只有getter、setter方法不同,实体一般由唯一标识ID和值对象组成,并且有自己的状态和行为,并且状态的变更也会影响到行为,状态与行为应该保持一致。DDD中将VO(View Object)这种只有getter、setter方法,没有状态、行为的对象称之为“贫血对象”,而Entity是属于“充血对象”的一种。对象是否拥有状态与行为,是判断对象是否贫血的依据。

例如:人通过身份证号作为唯一标识,具有唯一性,我们也有自己状态,与自己的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Person {

//Id作为唯一标识
private String idCard;

//姓名
private String name;

//年龄
private String age;

//性别
private Integer sex;

//家庭住址
private Address address;

//是否已成年, 表示状态
public boolean isAdult() {
return age >= 18;
}

//喝酒
public void drinkWine(){
if(!isAdult()) {
System.out.print("未成年人不能饮酒!")
} else {
System.out.print("吨吨吨。。。")
}
}
}
Value Object(值对象)

没有唯一标识,不具备唯一性,具有校验、判断逻辑,只关心对象的值,没有状态和行为。

值对象,顾名思义这是一个由“值”组成的对象。“值”代表的含义,其实就是指没有状态的属性。

例如:在一个订单领域中,Address只作为地址信息来说,它就不具备唯一性,作为一个值对象,它没有任何行为。当然你也可以为Address赋予唯一的含义与标识,在地图领域中,Address可能代表的是北京、或者上海,你可以给它加上唯一标识,但此时,他也就不是一个值对象了。也就是说,一个对象在DDD的概念中,对象代表的概念,并不是非黑即白。当它处于不同的领域上下文时,代表着不同的含义,其实也就是上面所说到的“限界上下文”这么一个概念。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Address {

private String city;

private String province;

private String district;

//获取完整地址
public String getCompleteAddress (){
return province + city + district
}
}
Aggregate(聚合)

聚合中所包含的对象之间具有密不可分的联系,一个聚合中可以包含多个实体和值对象,因此聚合也被称为根实体。

聚合在DDD中是跟领域挂钩的,它通过实体与值对象组成;本质上就是建立了一个比对象粒度更大的边界,用于聚集那些紧密关联的对象,形成了一个业务上的对象整体。例如下面这个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Emp {

//员工ID,值对象
private AgentId agentId;

//员工基础信息, 值对象
private EmpBaseInfo empBaseInfo;

//个人身份信息,实体
private Person person;

//工作地址
private Address address;
}

//员工基本信息
public class EmpBaseInfo {

//花名
private String nickName;

//职级
private Integer level;

}
Factories(工厂)

复杂对象的创建是领域层的职责,但这项任务并不一定属于那些用于表示模型的对象,他们没有对应模型中的事物,但又确实承担了领域层的职责。应该将创建复杂对象和聚合的职责转移给单独的对象,这个对象本身可能没有承担领域模型中的职责,但它仍然是领域设计的一部分。提供一个封装所有复杂装配操作的接口,而且这个接口不需要客户引用要被实例化的对象的具体类。在创建聚合时要把它作为一个整体,并确保它满足固定规则。

Repositories(仓储)

日常开发中,我们经常会用到到Repositories,作为一个Dao,它主要负责操作DO对象,与底层数据库进行交互。但是在DDD中,Repositories不等于Dao,Repository对应的是对Entity对象读取储存的抽象,在接口层面做统一,不关注底层实现。Repository的具体实现类通过调用Dao来实现各种操作,通过Builder/Factory对象实现DO到Entity之间的转化。DDD中数据资源的访问与维护只能由Repositories来操作。

Domain Service (领域服务)

当某个行为不属于任何一个领域时,那么就需要领域服务来涉及多个领域对象的操作。<阿里技术专家详解DDD系列>>中就举了一个玩家打怪兽的例子,如果玩家有攻击怪兽这么一个行为,映射到我们领域模型中,那么是在玩家领域中攻击了怪兽,还是怪兽领域中被玩家攻击,此时在代码层面,攻击野兽这个行为,无论放到哪个领域中都不太合理,因此需要一个领域服务来专门处理这种跨领域的行为。

Domain Primitive(基础类型)

Domain Primitive 是一个在特定领域里,拥有精准定义的、可自我验证的、拥有行为的 Value Object。

Domain Primitive(DP)是Value Object的进阶版,在原始VO的基础上要求每个DP拥有概念的整体,而不仅仅是值对象。其主要作用:

  • 让隐性的概念显性化
  • 让隐性的上下文显性化
  • 封装多对象行为

常见的 DP 的使用场景包括:

  • 有格式限制的 String:比如Name,PhoneNumber,OrderNumber,ZipCode,Address等
  • 有限制的Integer:比如OrderId(>0),Percentage(0-100%),Quantity(>=0)等
  • 可枚举的 int :比如 Status(一般不用Enum因为反序列化问题)
  • Double 或 BigDecimal:一般用到的 Double 或 BigDecimal 都是有业务含义的,比如 Temperature、Money、Amount、ExchangeRate、Rating 等
  • 复杂的数据结构:比如 Map<String, List> 等,尽量能把 Map 的所有操作包装掉,仅暴露必要行为

DDD分层架构

前面战术设计主要介绍了,DDD中的一些基本概念。现在我们就来讲讲DDD在我们工作中如何具体的实施,不同于传统的三层模型,Controller、Service、Dao不同,DDD的分层更加复杂,从下图可以看出架构中的层级关系。

Interaface(接口层)

接口层处于架构的最上层,提供系统对外暴露的接口。主要功能是接收外部的请求,调用底层的服务,然后将底层服务返回的数据返回给调用者。这一层只包含对外的DTO对象的声明,接口声明,DTO对象转换,日志打印等。不能包含因为逻辑。

Application(应用层)

应用层描述了整个系统的全部功能,它不实现业务逻辑,只对底层的领域对象进行编排以实现用例。一般在这一层里使用Repositories(仓储)对数据进行读取和保存。事务处理一般也在这一层。这一层主要包括Service,用来调用Domain层的对象完成一个业务。访问第三方的远程调用一般也是在这一层。

Domain(领域层)

Domain模块是核心业务逻辑的集中地,包含有状态的实体、聚合、领域服务Domain Service、以及各种外部依赖的接口类(如Repository、ACL、中间件等。领域层出现的pojo对象只有领域对象,不能出现别的DTO,DO,VO这些对象;

Infrastructure(基础设施层)

负责所有的对外的交互。比如数据库访问层实现,RPC接口,MQ等。

服务视图

DDD实战

1
2


接下来我们将从实际的业务出发

未完待续。。。

参考文档

https://ld246.com/article/1574177012526

https://zhuanlan.zhihu.com/p/366395817

https://insights.thoughtworks.cn/ddd-aggregation-bounded-context/

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×