导图社区 领域驱动设计
领域驱动设计知识点总结,软件设计必备,主要内容有DDD入门之理解面向对象、概念、分层架构、领域建模、CQRS。
编辑于2022-03-18 17:02:41领域驱动设计(DDD)
1 DDD入门之理解面向对象
面向对象编程的误解
我们把面向对象里的“对象”理解错了,我们理解成了语法层面的对象。所以我们的代码才会出现所谓的贫血模型。
面向对象编程里的“对象”是什么?
封装了数据和行为的东西
代码有体现面向对象吗?
没有。语法层面对象的3大特点(封装、继承、多态),我们的代码都有体现。 但是对象里要么是只有数据,没有行为(POJO)。要么是只有行为,没有数据(Serivce)。即所谓的贫血模型。 定义POJO,封装了属性和getter&setter,看似具有封装性,其实仅仅是一个数据容器而已。因为没有把数据和行为封装起来,行为都放在Service里了。
真正的面向对象
面向的是生活中的真实对象,用代码的方式模拟真实的对象。
2 概念
领域、子域、核心域、通用域、支撑域
领域:领域就是用来确定范围的,范围即边界,领域就是这个边界内要解决的业务问题域。
子域:领域可以进一步划分为子领域。把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围。
核心域:决定产品和公司核心竞争力的子域是核心域,它是业务成功的主要因素和公司的核心竞争力。
通用域:没有太多个性化的诉求,同时被多个子域使用的通用功能子域是通用域。
支撑域:还有一种功能子域是必需的,但既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域,它就是支撑域。
为什么要划分核心域、通用域、支撑域? 公司在 IT 系统建设过程中,由于预算和资源有限,对不同类型的子域应有不同的关注度和资源投入策略,记住好钢要用在刀刃上。
限界上下文
通用语言:团队内部能够清晰、准确的描述业务模型的语言就是通用语言。 例如 通用业务术语,在代码模型设计的时侯要建立领域对象和代码对象的一一映射
限界上下文:用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。
实体和值对象
实体拥有唯一标识符,一般对应业务对象,具有业务属性和业务行为
值对象主要是属性集合,描述实体的状态和特征
聚合和聚合根
聚合就是由业务和逻辑紧密关联的实体和值对象组合而成的,聚合是数据 修改和持久化的基本单元
如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根也称为根实体,它不仅是实体,还是聚合的管理者。
如何设计聚合?
聚合用来封装真正的不变性,而不是简单地将对象组合在一起。
假如有以下不变条件,c=a+b,当a等于2, b等于3时,c必定等于5。根据这条规则,如果c不为5,那么我们便违背了系统的不变条件。 聚合用来封装真正的不变性,而非简单地组合对象。聚合内有一套不变的业务规则,各实体和值对象按统一业务规则运行以实现对象数据的一致性,边界之外的任何东西都与该聚合无关,这就是聚合能实现高内聚的原因。
设计小聚合
通过唯一标识引用其它聚合
聚合之间是通过关联外部聚合根ID的方式引用,而不是直接对象引用的方式。外部聚合的对象放在聚合边界内管理,容易导致聚合的边界不清晰,也会增加聚合之间的耦合度。
在边界之外使用最终一致性
聚合内数据强一致性,而聚合间数据最终一致性 在一次事务中,最多只能更改一个聚合的状态。如果一次业务操作涉及多个聚合状态的更改,应采用领域事件的方式异步修改相关的聚合,实现聚合间解耦。 在不持有对象引用的情况下,不能修改其他聚合,因此我们可以避免在同一个事务中修改多个聚合。但这种方式的缺点在于限制性太强,因为在领域模型中我们总需要对象之间的关联关系来完成一些任务。 那么,此时我们应该怎么办呢?
通过应用层实现跨聚合的服务调用
总结
聚合的特点 高内聚、低耦合,它是领域模型中最底层的边界,可作为拆分微服务的最小单位,但不推荐过度拆分。 一个微服务可包含多个聚合,聚合之间的边界是微服务内天然的逻辑边界。有了该逻辑边界,在微服务架构演进时就可以聚合为单位进行拆分和组合。
聚合根的特点 聚合根是实体,有实体的特点,具有全局唯一标识,有独立的生命周期。一个聚合只有一个聚合根,聚合根在聚合内对实体和值对象采用直接对象引用的方式进行组织和协调,聚合根与聚合根之间通过ID关联的方式实现聚合之间的协同。
实体的特点 有ID标识,通过ID判断相等性,ID在聚合内唯一即可。状态可变,它依附于聚合根,其生命周期由聚合根管理。实体一般会持久化。实体可引用聚合内的聚合根、实体和值对象。
值对象的特点 无ID,不可变,无生命周期,用完即扔。值对象之间通过属性值判断相等性。它的核心本质是值,是一组概念完整的属性组成的集合,用于描述实体的状态和特征。值对象尽量只引用值对象。
3 分层架构
用户接口层
用户接口层负责向用户显示信息和解释用户指令
应用层
应用层是很薄的一层,理论上不应该有业务规则或逻辑,但应用层又位于领域层之上,因为领域层包含多个聚合,所以它可以协调多个聚合的服务和领域对象完成服务编排和组合,协作完成业务操作。
领域层
领域层的作用是实现企业核心业务逻辑,通过各种校验手段保证业务的正确性。领域层主要体现领域模型的业务能力,它用来表达业务概念、业务状态和业务规则。 领域层包含聚合根、实体、值对象、领域服务等领域模型中的领域对象。
基础层
基础层是贯穿所有层的,它的作用就是为其它各层提供通用的技术和基础服务,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。比较常见的功能还是提供数据库持久化。
三层、四层对比
4 领域建模
概述
领域建模时,我们会根据场景分析过程中产生的领域对象,比如命令、事件等之间关系找出产生命令的实体,分析实体之间的依赖关系组成聚合,为聚合划定限界上下文,建立领域模型以及模型之间的依赖。
1、从命令和事件中提取产生这些行为的实体。
2、根据聚合根的管理性质从实体中找出聚合根。
有些业务场景可能无法设计出典型的领域模型。这类业务中有多个实体,实体之间相互独立,是松耦合的关系,找不出聚合根,但就业务本身来说它们是高内聚的。
3、划定限界上下文,根据上下文语义将聚合归类。
5 CQRS
CQRS 是“命令查询责任分离”(Command Query Responsibility Segregation)的缩写
CQRS模式
单进程单实体 + 共享存储/共享模型
对于简单的单体或者微服务应用,这种方式是最自然最直接的方式,事实上我们并不需要太多设计上的思考便能想到这种方式。在这种方式中,存在单个领域实体模型同时用于读写操作,在向调用方返回查询数据时,需要针对性地对领域模型进行转换。
单进程单实体 + 共享存储/分离模型
有时,即便是对于单个实体,其查询也会变得复杂,为了维护读写过程彼此的清晰性,我们可以对读模型和写模型分别建模,事实上这也是CQRS的本意。
单进程跨实体 + 共享存储/分离模型
既然单个实体都有必要使用分离模型,那么在同一个进程空间中的跨实体查询更有理由使用分离模型的形式。对于简单形式跨实体查询,还用不着使用分离的存储,只需要做一些join联合查询即可。
单进程跨实体 + 分离存储/分离模型
先前的join操作太复杂或者太低效了,需要采用专门的数据库来简化查询提升效率。
跨进程跨实体 + 分离存储/分离模型
在"跨进程跨实体 + 分离存储/分离模型"中,存在一个单独的查询服务用于CQRS的读操作,查询所需数据通常通过事件机制从不同的其他业务服务中同步而来,读操作所返回的数据通过API Gateway向外暴露