什么是DDD?
Eric Evans 在其著作《领域驱动设计:软件核心复杂性应对之道》提出了一种新的架构设计方法——领域驱动设计(Domain Driven Design)简称DDD。对于初次接触DDD的开发同学而言,一些概念理解起来可能比较晦涩,在此把DDD的几个基础概念进行梳理记录。
DDD中的那些概念
1、领域
领域在我们的认知当中和区域、地盘这些词都属于近义词,从字面意思上也可以看出来领域是用来确定范围和边界的。在我们进行业务建模时,DDD会按照一定的规则将业务领域进行细分,当领域细分到一定的程度后,我们便可以将业务上的问题将其限定归属在特定的边界内,而这划分出来的一个个边界就可以叫做领域。
既然领域是用来限定业务边界和范围的,就会有大小之分,拆分出来的一个个子领域我们便可以叫做子域。
库内-》复拣
2、子域
为了降低业务理解和系统实现的复杂度,我们将问题域逐步划分成为我们能够理解的小问题,而划分形成的一个个小边界就是子域,子域根据自身重要性和功能属性又可以划分为三类子域:
- 核心域(订单)
- 核心域决定产品和系统核心竞争力的子域是核心域,它是影响产品和业务成功的主要因素
- 通用域(权限管理)
- 通用域没有太多个性化的诉求,同时被多个子域使用的通用功能子域是通用域
- 支撑域()
- 支撑域不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域,但该功能子域又是产品所必须的,它就是支撑域
3、限界上下文
对应于通用语言,限界上下文是语言的边界;对于领域模型,限界上下文是模型的边界;
限界上下文将问题空间(Problem Space)限定在一个有限的范围内
不同域之间往往会有冲突的概念,限界上下文会将这些概念区隔开,概念只需要在限界上下文中有统一语言,并且不存在歧义即可。
限界上下文是一个显式的概念性边界,领域模型都存在于这个边界之内。这个边界之内的每种领域术语、词组或句子我们也叫做通用语言,我们在设计时要保证某个通用语言在一个限界上下文内一定只具有唯一的语义。
限界上下文在微服务设计中比较重要,它是微服务设计和拆分的主要依据,一般将限界上下文内的领域模型映射到微服务,就完成了从问题域到软件的解决方案,一个限界上下文理论上就可以设计为一个微服务。
设备库与货品库关于Sku的歧义
4、实体
实体是领域模型中的一类核心的领域对象,它们拥有唯一标识符,且这个标识符在整个软件生命周期内都不会发生变化,即使除了唯一表示符外的所有属性信息都发生了变化,其仍旧代表同一对象。对这些对象而言,重要的不是其属性,而是其延续性和标识,我们把这样的对象称为实体。
我们目前代码中用来和数据库层打交道的DO对象是不是和上面的描述很类似。但不同的是我们目前用到的DO对象,一般只有getter和setter方法,而DDD中的实体会封装包含这个实体相关的所有业务逻辑,它不仅是多个业务属性的载体,也是操作或行为的载体,也就是我们通常所说的充血模型。
5、值对象
介绍完实体后,便不得不提值对象。在介绍概念之前,我们先举个例子,比如我们针对入职领域设计了一个员工实体,包括姓名、工号、性别以及员工通讯地址的国家、省、市、县、街道、邮政编码等属性,然后在这个实体上提供了一个员工地址保存的方法,如果此时在领域服务层另一个实体想要调用员工实体的地址保存方法,则方法的入参就需要6个参数,这样一方面让领域模型的属性设计变得很零碎,写代码时调用过程也变得繁琐起来。而我们随其自然的就会想到,将这些省、市、县等属性打包成为一个地址属性集合,而这个属性集合我们便可以叫做值对象。
值对象用来描述领域的特定方面,并且是一个没有标识符的对象,书中对其的定义是:
通过对象属性来识别的对象,它将多个相关属性组合为一个概念实体
值对象与实体的区别在于:
- 值对象一般依附于实体而存在, 是实体属性的一部分,而非独立存在。
- 值对象没有唯一标识, 或者说值对象的全部属性合在一起作为唯一标识,。所以,当任何属性发生变化时, 都意味着新的值对象产生。
- 实体的业务功能非常丰富,是业务对象的基本单元。而值对象功能单一,一般是贫血模型没有过多的修改操作,生命周期依赖于实体的,实体没有了,值对象也就没有了。
在设计领域模型时,是否要要将领域对象设计成值对象,可以看这个对象是否后续还会来回修改,会不会有生命周期。如果不可修改,并且以后也不会专门针对它进行查询或者统计,我们就可以把它设计成值对象,反之则建议设计为实体。
货品库的条码
6、聚合
上面的实体和值对象都是基础的领域对象,它们一般都是个体化的,表现出来的也是个体的能力。而要构建一个复杂的业务,仅仅依靠个体的能力是难以完成的,需要多个实体和值对象一起来协同工作,而用来协同工作的这个组织就是聚合。
聚合是由业务和逻辑紧密关联的实体和值对象组合而成的,聚合是数据修改和持久化的基本单元,用聚合来确保这些领域对象在实现共同的业务逻辑时,能够保证数据的一致性。
聚合在DDD的分层架构中属于领域层,领域层包含了多个聚合,共同来实现核心业务逻辑。同一个聚合内我们要实现事务一致性,聚合之内的操作不能违背这个原则。这也就要求我们在设计时要将聚合设计的尽量小到不可拆分,过大聚合在查询时需要加载的数据很多,涉及到的实体也会较多,会降低查询和修改的性能。
货品库(货主/多条码)
7、聚合根
聚合根也称为根实体,是聚合的管理者,上面将聚合比作组织,那么聚合根便是这个组织的负责人。聚合根在定义中提到也可以叫做根实体,可以了解到它其实也是一个实体,具备唯一标识(也称:聚合根ID),拥有实体的属性和业务行为,有着自己的业务逻辑。只不过这个实体,还用来作为聚合的管理者,在聚合内部负责协调实体和值对象按照固定的业务规则,协同完成共同的业务逻辑。
简单解析概念
- 聚合(aggregate / agg):几个实体和值对象聚拢在一起协同工作的组织就是聚合
- 实体(entity):有唯一标识的核心领域对象,上文中通过用例定位出来的一些明显很重要的名词
- 值对象(value object):值对象依附于实体存在,一些相关的实体属性打包在一起处理,而形成的对象
- 聚合根(aggregate root / agg root):一个特殊的实体,是聚合的入口,抓住聚合根可以拎起整个聚合
- 领域方法 (domain service):用来存放一些跨多个实体,且无处安放的逻辑;
- 限界上下文(bounded context):
领域建模的方式有很多种
- 用例分析法
- UserStory
- 四色建模法
- EventStorming
- ……
方式手段很多,但目的都是获得问题域的领域模型,各个方式可能在建模效率,建模质量层面有些许异同;
最后:
- DDD不是银弹,不一定适用于所有场景
- 在真实的业务逻辑里,我们的领域模型或多或少的都有一定的“特殊性”,如果100%的要符合DDD规范可能会比较累,所以最主要的是梳理一个对象行为的影响面,然后作出设计决策
附录
一个不错的DDD思维导图
这个是DDD的实践案例,也是一位大佬落地后的心得。感兴趣的可以了解下
https://juejin.cn/post/6844904177207001101
https://juejin.cn/post/6844904201575743495
https://juejin.cn/post/6845166890554228744
https://juejin.cn/post/6912228908075057166
https://juejin.cn/post/6953141151931039758