这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战
这期打算斌带着问题边进行学习归纳
本文主要分为以下几个部分
- DDD基本信息
-
- 什么是DDD
- 适用场景
- DDD主要概念
-
- 实体、值对象
- 充血模型、贫血模型
- 领域及其子概念
- 聚合、聚合根
- 防腐层
- 领域事件
- DDD战略设计
-
- 产品愿景
- 功能分析
- 领域建模
- DDD战术设计
-
- 领域对象分析
- 项目结构设计
- 领域以及领域对象组织
- 代码目录设计
- DDD误区
在项目的重构中,我们团队也用到了DDD的思想进行项目的设计,在这个过程中也产生了不少的疑问与心得
疑问:
- 当实体A中的某个属性是实体B的列表,同时实体A实体B都可以独立存在、独立使用,实体A、B是属于同一个领域还是属于不同的领域?
- 部分逻辑较为独特,不属于复用性强的逻辑,和某个服务层方法强绑定,但是又只和单一领域相关,那么它应该放在对应领域层还是服务层?
- 如何有效区分领域对象方法(实体方法、值对象方法)与领域方法?
- 领域层之间的相互依赖怎么实现?
- repository层的相互依赖怎么实现?
- 业务并没有那么复杂,需要完整实现整个DDD结构吗?
一些心得:
- DDD可以清晰地划分各个业务领域,提供微服务划分指导
- DDD可以根据实际的场景进行调整
- 更多的是需要与团队达成共识
-
- 形成统一的项目结构共识
- 共同形成代码风格上的一致,使得项目本身可以更加健康的长期维护
DDD基本信息
什么是DDD?
领域驱动设计,面对复杂业务,清晰划分业务边界, 解耦业务,达到业务模型与代码模型保持一致
解耦业务意味着可以指导微服务的划分
适用场景
业务较为复杂,模块概念较多
微服务边界划分困难
DDD主要概念
实体、值对象
实体和值对象是领域服务中的业务对象
两者都拥有自己的对象方法
实体:
- 承载领域服务、拥有主要属性的类
- 有明确的身份信息
-
- 即使属性一样,只要身份标识(如ID)不一样,也就不一样
包含实体属性与方法:
- 方法只关心自身变化
-
- 实体与其他实体的之间的业务逻辑应该在领域层
- 通常采用充血模型
值对象:
- 没有明确的身份信息,或者说身份信息没有意义
-
- 只关心其属性值,如果都相同,那么这两个值对象相同
也有自己的类方法与属性:
- 通常为贫血模型
贫血模型与充血模型
充血模型:
只包含属性(业务数据)不包含方法
充血模型:
不仅仅包含属性(业务数据),还包含了该对象的自身的业务逻辑
典型的面向对象编程
领域及其子概念
领域即业务边界、业务的功能范围
根据重要程度以及功能划分以下三种:
- 核心域:业务核心模块
- 通用域:具有一定的通用性的服务
- 支撑域:具有一定的特殊性,但是是基础支撑服务
领域层不关心下一层的Repos层的实现以及数据持久化形式
聚合以及聚合根
聚合:
将领域内的实体、值对象进行组织向外提供服务
- 实体采用充血模型,实现实体个体业务逻辑
-
- 包括实体职能
- 跨实体业务逻辑用领域服务实现(于聚合内)
- 跨领域的业务逻辑于应用服务层(application层)实现
聚合高内聚、低耦合
多个聚合(领域)形成界限上下文,一个界限上下文实际上是一个application,为微服务的划分边界
聚合根
为实体,是该领域中的主要业务对象,是该聚合的管理者
- 该聚合(领域)的任何操作都应该经过管理者之手
防腐层
在我的理解中,就是一种转换
可以是任何带有转换作用的东西
- 可以是项目中不同层之间对象转换的代码
- 可以是微服务之间传输对象与应用服务对象的中间代码
- 可以是新旧服务迁移,兼容数据转换的代码
- 可以是同作用、来源不同且异构的获取、转换代码
- 部分上述场景中的代码可以换成服务
领域事件
领域之间可能会有协作与相互影响关系
从根本上说,实际上是对于特定领域来说,发生某件事情会影响自身的状态(触发某些事件)
也就是领域会受到事件影响
- 采用事件机制实现
-
- 处于/不处于同一个微服务的领域实现事件机制的方式可能不一样
-
- 同一微服务:事件总线
- 不同微服务:共享资源
-
- 消息中间件
- 轮训数据库状态
- 监听binlog
- 事件是否需要持久化?
- 事件失败
-
- 事件重试
- 事件幂等
- 事件补偿
- 采用多个共享资源的时候,可能存在一致性问题
DDD战略设计
侧重模型分析、业务分析,建立整个DDD模型
产品愿景
需要开发人员、领域专家、需求方、产品经理、架构师坐在一起事件风暴,确认以下几个点
- 项目目标是什么
- 项目的目标用户、核心价值、差异化竞争点
场景分析
为了探索产品中的主要场景、操作用例
从而达到指导建立领域模型的目的
领域建模
- 确认实体、值对象等领域对象
- 寻找聚合根
-
- 在同一个业务范围内紧密合作的实体值对象
- 找到该业务范围内的主导实体,为聚合根
- 允许无聚合根,多实体共同完成领域内业务,采用传统方法进行实体管理
- 确定聚合(确定领域)
-
- 确定聚合方法(领域方法)
- 确定界限上下文(application)
-
- 如何划分界限上下文?
- 同个界限上下文可以划分出多个application
-
- 如何拆分application?
-
- CQRS:将命令(会影响数据的操作)与查询(数据无影响)操作划分到不同application
- 根据操作目的的主要业务实体进行划分
-
- 如求一个工资单内所有工资条的实发金额的和,主要目的业务实体为工资单
- 根据操作人身份划分
- 确定application层方法
DDD战术设计
侧重模型落地,考虑模型内部实现细节
领域对象分析
- 确认每个领域对象应该是实体还是值对象
-
- 确定对象属性以及方法
- 确定领域方法实现
- 确定application层方法实现
对象定义
application层:
- AppService:应用服务(构成应界限上下文)
- DTO/Model:data transform object,暴露给外部的传输对象
domain(聚合)层:
- DomainService:领域服务,该聚合的服务体现,管理DO、VO,包含Repos属性
- DO:domain object,领域实体
- VO:value object,值对象
- Repos:仓储层Interface抽象
Persistent(仓储层):
- ReposImple:仓储层具体实现,仓储层给上层的是DO,转换可能会依赖多个库(DAO层)
- PO:Persistent Object,数据库表结构对应对象
- DAO:数据库表对象方法对应实现
对象关系:
实线框:对象 密虚线框:独立方法 疏虚线框:DDD概念范围
实体间的包含关系通过依赖注入的方式进行初始化
实体只能使用自己内部的实体、基础工具
项目结构设计
调用关系以及协作
调用层级关系:
含有跨领域(聚合)的调用关系:
DDD误区
所有地方都要DDD
- DDD适合用于业务逻辑较为复杂的业务
- 需要组内成员对DDD有所心得,并且有使用上的共同约定
完全按照DDD进行战术设计
战术设计和战略设计不一样的是
战术设计是战略设计的落地实现,实现方式有很多,可以采取DDD这种划分领域层存储层、划分实体、聚合管理实体值对象、通过事件进行业务影响扩散等
如果业务较为简单,也可以直接像传统方式,直接CURD即可
重战术轻战略
战略是战术基础,战略不清晰,战术易重构