领域驱动设计笔记
领域驱动设计笔记
领域
DDD 的领域就是这个边界内要解决的业务问题域。
子域
我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围。
在领域不断划分的过程中,领域会细分为不同的子域,子域可以根据自身重要性和功能属性划
分为三类子域,它们分别是:核心域、通用域和支撑域
核心域、支撑域和通用域的主要目标是:通过领域划分,区分不同子域在公司内的不同功能属
性和重要性,从而公司可对不同子域采取不同的资源投入和建设策略,其关注度也会不一样
通用语言定义上下文含义,限界上下文则定义领域边界
在事件风暴过程中,通过团队交流达成共识的,能够简单、清晰、准确描述业务涵义和规则的语言就是通用语言
设计过程中我们可以用一些表格,来记录事件风暴和微服务设计过程中产生的领域对象及其属性,比如,领域对象在 DDD 分层架构中的位置、属性、依赖关系以及与代码模型对象的映射关系等
DDD 分析和设计过程中的每一个环节都需要保证限界上下文内术语的统一,在代码模型设计的时侯就要建立领域对象和代码对象的一一映射,从而保证业务模型和代码模型的一致,实现业务语言与代码语言的统一。
限界上下文
限界上下文的定义就是:用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性
理论上限界上下文就是微服务的边界。我们将限界上下文内的领域模型映射到微服务,就完成了从问题域到软件的解决方案。
实体与值对象
在 DDD 中有这样一类对象,它们拥有唯一标识符,且标识符在历经各种状态变更后仍能保持一致。对这些对象而言,重要的不是其属性,而是其延续性和标识,对象的延续性和标识会跨越甚至超出软件的生命周期。我们把这样的对象称为实体
值对象的定义:通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体
简单来说,值对象本质上就是一个集合。若干个用于描述目的、具有整体概念和不可修改的属性。在领域建模的过程中,值对象可以保证属性归类的清晰和概念的完整性,避免属性零碎。
在领域建模时,我们可以将部分对象设计为值对象,保留对象的业务涵义,同时又减少了实体的数量;在数据建模时,我们可以将值对象嵌入实体,减少实体表的数量,简化数据库设计。
聚合及聚合根
聚合就是由业务和逻辑紧密关联的实体和值对象组合而成的,聚合是数据修改和持久化的基本单元,每一个聚合对应一个仓储,实现数据的持久化
跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务逻辑通过应用服务来实现
聚合的一些设计原则
1. 在一致性边界内建模真正的不变条件
2. 设计小聚合
3. 通过唯一标识引用其它聚合
**4. 在边界之外使用最终一致性。**聚合内数据强一致性,而聚合之间数据最终一致性。一次事务最多只能更改一个聚合的状态。如果一次业务操作涉及多个聚合状态的更改,应采用领域事件的最终一致性
5. 通过应用层实现跨聚合的服务调用
聚合根的主要目的是为了避免由于复杂数据模型缺少统一的业务规则控制,而导致聚合、实体之间数据不一致性的问题
聚合之间通过聚合根 ID 关联引用,如果需要访问其它聚合的实体,就要先访问聚合根,再导航到聚合内部实体,外部对象不能直接访问聚合内实体。
**聚合的特点:**高内聚、低耦合,它是领域模型中最底层的边界,可以作为拆分微服务的最小单位,但我不建议你对微服务过度拆分。但在对性能有极致要求的场景中,聚合可以独立作为一个微服务,以满足版本的高频发布和极致的弹性伸缩能力。
一个微服务可以包含多个聚合,聚合之间的边界是微服务内天然的逻辑边界。有了这个逻辑边界,在微服务架构演进时就可以以聚合为单位进行拆分和组合了,微服务的架构演进也就不再是一件难事了。
**聚合根的特点:**聚合根是实体,有实体的特点,具有全局唯一标识,有独立的生命周期。一个聚合只有一个聚合根,聚合根在聚合内对实体和值对象采用直接对象引用的方式进行组织和协调,聚合根与聚合根之间通过 ID 关联的方式实现聚合之间的协同。
**实体的特点:**有 ID 标识,通过 ID 判断相等性,ID 在聚合内唯一即可。状态可变,它依附于聚合根,其生命周期由聚合根管理。实体一般会持久化,但与数据库持久化对象不一定是一对一的关系。实体可以引用聚合内的聚合根、实体和值对象。
**值对象的特点:**无 ID,不可变,无生命周期,用完即扔。值对象之间通过属性值判断相等性。它的核心本质是值,是一组概念完整的属性组成的集合,用于描述实体的状态和特征。值对象尽量只引用值对象。
领域事件
领域事件是领域模型中非常重要的一部分,用来表示领域中发生的事件。一个领域事件将导致进一步的业务操作,在实现业务解耦的同时,还有助于形成完整的业务闭环
领域事件驱动设计可以切断领域模型之间的强依赖关系,事件发布完成后,发布方不必关心后续订阅方事件处理是否成功,这样可以实现领域模型的解耦,维护领域模型的独立性和数据的一致性。在领域模型映射到微服务系统架构时,领域事件可以解耦微服务,微服务之间的数据不必要求强一致性,而是基于事件的最终一致性
跨微服务的事件机制要总体考虑事件构建、发布和订阅、事件数据持久化、消息中间件,甚至事件数据持久化时还可能需要考虑引入分布式事务机制等。
领域事件总体架构
DDD分层架构
1.用户接口层
用户接口层负责向用户显示信息和解释用户指令。这里的用户可能是:用户、程序、自动化测试和批处理脚本等等。
2.应用层
应用层是很薄的一层,理论上不应该有业务规则或逻辑,主要面向用例和流程相关的操作。但应用层又位于领域层之上,因为领域层包含多个聚合,所以它可以协调多个聚合的服务和领域对象完成服务编排和组合,协作完成业务操作。此外,应用层也是微服务之间交互的通道,它可以调用其它微服务的应用服务,完成微服务之间的服务组合和编排。
3.领域层
领域层的作用是实现企业核心业务逻辑,通过各种校验手段保证业务的正确性。领域层主要体现领域模型的业务能力,它用来表达业务概念、业务状态和业务规则。
领域层包含聚合根、实体、值对象、领域服务等领域模型中的领域对象。
4.基础层
基础层是贯穿所有层的,它的作用就是为其它各层提供通用的技术和基础服务,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。比较常见的功能还是提供数据库持久化。
基础层包含基础服务,它采用依赖倒置设计,封装基础资源服务,实现应用层、领域层与基础层的解耦,降低外部资源变化对应用的影响。
DDD 分层架构有一个重要的原则
每层只能与位于其下方的层发生耦合。
在严格分层架构中,领域服务只能被应用服务调用,而应用服务只能被用户接口层调用,服务是逐层对外封装或组合的,依赖关系清晰。而在松散分层架构中,领域服务可以同时被应用层或用户接口层调用,服务的依赖关系比较复杂且难管理,甚至容易使核心业务逻辑外泄。
三层架构向 DDD 分层架构演进
中台本质上是领域的子域,它可能是核心域,也可能是通用域或支撑域。通常大家认为阿里的中台对应 DDD 的通用域,将通用的公共能力沉淀为中台,对外提供通用共享服务
微服务架构模型
发展历程
DDD分层架构
六边形架构(端口适配器架构)
整洁架构(洋葱架构)
项目级微服务
企业级中台微服务
事件风暴建模法
正向驱动结合反向驱动
正向驱动
列出领域事件
确定领域事件
确定决策命令
确定用户或策略
确定聚合
选定下一个领域事件
反向驱动
选定领域事件
确定读模型
由读模型驱动出上游领域事件
微服务的服务调用
服务的封装与组合
数据对象视图
微服务设计原则
第一条:要领域驱动设计,而不是数据驱动设计,也不是界面驱动设计
第二条:要边界清晰的微服务,而不是泥球小单体
第三条:要职能清晰的分层,而不是什么都放的大箩筐
第四条:要做自己能 hold 住的微服务,而不是过度拆分的微服务
在遵守领域边界和微服务分层等大原则下,在进行战术层面设计时,我们应该选择最适合的方法,不只是 DDD 设计方法,当然还应该包括传统的设计方法。这里要以快速、高效解决实际问题为最佳,不要为做 DDD 而做 DDD
微服务拆分需要考虑哪些因素
1. 基于领域模型
2. 基于业务需求变化频率
3. 基于应用性能
4. 基于组织架构和团队规模
5. 基于安全边界
6. 基于技术异构等因素
子域和限界上下文的关系
子域的划分是一种比较粗的领域边界的划分,它不考虑子域内的领域对象、对象之间的关系和结构。子域的划分往往按照业务阶段或者功能模块边界进行粗分,其目的就是为了让你能够在一个相对较小的问题空间内,比较方便地用事件风暴来梳理业务场景。
而限界上下文本质上也是子域,限界上下文是在明确的子域内,用事件风暴划分出来的。它体现的是一种详细的设计过程。这个过程设计出了领域模型,明确了领域对象以及领域对象的依赖等关系,有了领域模型,你就可以直接进行微服务设计了。
子域是面向问题空间,而限界上下文是面向解决方案空间
参考课程: