DDD领域驱动设计[归纳]
2020-05-12 15:25:53 举报
AI智能生成
登录查看完整内容
相似推荐
查看更多
DDD领域驱动设计
(DDD)实现领域驱动设计
DDD领域驱动设计
DDD领域驱动设计
DDD实战,领域驱动设计
领域驱动设计(DDD)
领域驱动设计
领域驱动设计(DDD)
DDD领域驱动设计
DDD领域驱动设计
DDD领域驱动设计 知识归纳
作者其他创作
大纲/内容
1.统一通用语言
2.需求分析
3.提炼名词实体以及实体英文
划分限界上下文
建模领域模型
4.从需求实例中四色建模
5.提炼实体(名词)与行为能力(动词)
6.划分核心域、通用域、支撑域
实体
值对象
聚合
应用服务
领域服务
领域事件
资源库
工厂
7.细分战术七要素的各个职责
2.划分领域
3.找出限界上下文
4.识别出核心域
(A.战略设计)
5.对领域进行建模
6.实体
7.值对象
8.聚合
9.领域服务
10.领域事件
(B.战术设计)
DDD落地步骤
规模:软件需求的线性增长导致了系统的规模
结构:系统不断完善的质量要求是决定系统复杂度的关键因素
变化:如果变化是不可预测的,那么软件系统也会变得不可预测
造成软件复杂度成因
分而治之、控制规模
六边形架构(Hexagonal Architecture)
整洁架构(The Clean Architecture)
保持结构的清晰与一致
可进化性(Evolvability)
可扩展性(Extensibility)
可定制性(Customizability)
拥抱变化:注重与变化有关的架构质量属性
控制软件复杂度的原则
分层架构的关注点分离
分层架构:关注点分离
六边形架构的内外分离
六边形架构:内外分离
隔离业务逻辑与技术实现:遵循“关注点分离”原则
针对庞大而复杂的问题域,限界上下文采用了“分而治之”的思想对问题域进行了分解,有效地控制了问题域的规模,进而控制了整个系统的规模。
限界上下文的分而治之
模型是封装,实现了对业务细节的隐藏;
模型是抽象,提取了领域知识的共同特征,保留了面对变化时能够良好扩展的可能性。
领域模型对领域知识的抽象
如何分辨业务复杂度与技术复杂度
需要封装的职责是否与领域相关
区别判断的标准
业务逻辑相关的服务
1.与横切关注点协作的服务应被定义为应用服务。
2.不包含领域逻辑的业务服务应被定义为应用服务。(???推演得出如下结论)【若应用服务要与领域交互,则尽可能将与横切关注点无关的纯领域行为往领域服务下推】
设计原则
与系统业务有关的领域逻辑
定义
核心关注点
在职责上是内聚的,但在使用上又会散布在所有对象层次中,且与所散布到的对象的核心功能毫无关系的关注点
日志
验证
在抽象层面属于业务行为,但在实现层面则属于基础设施的技术内容
分布式通信(调用外部服务)
例子
消息验证
错误处理
监控
事务
认证与授权
基础架构问题
横切关注点
分布式调用服务的正确做法:在领域层定义一个抽象的服务接口,然后通过依赖注入的方式注入到领域服务中
调用库存服务
调用购物车服务
4.验证(订单是否有效)
5.提交订单
6.移除购物车中已购商品
7.日志(记录业务相关操作)
8.异常处理(处理业务相关异常)
1.身份认证与授权
2.事务管理
3.监控
4.验证(请求参数的正确性)
7.日志(记录代码异常方便排查)
8.异常处理(处理代码相关异常)
9.发送邮件通知买家
“下订单”用例
如何分辨应用服务与领域服务
本质上,四⾊建模法获 得的并⾮领域模型,⽽是企业运营模型
四色建模法的对领域横向切分⽅式,正好与 Pace-Layered 的分层原则契合
要识别时标型对象,需要先梳理出业务流程,尤其是核⼼的业务流程
都提倡对核心领域进⾏划分
联系
领域驱动设计是从价值的⻆度来 划分核⼼领域和⼦领域,这是⼀种对领域的纵向切分
四⾊建模法则不然,它是从企业运营和管理的⻆度进⾏划分,可以认为是对领域的横向切分。
因为企业运营贯穿业务流程的始终,只要业务动作产⽣的结 果影响到企业的运营,都将作为⼀种财务的凭证⽽被记录下来,作为时标型对象加⼊到四⾊建模法获 得的模型中。
区别
如何分辨领域驱动设计与四色建模法
具有可追溯性的记录运营或管理数据的时刻或时段对象,⽤粉红⾊表示
在进⾏领域分析建模时,要从运营管理的⻆度去建模,从⽽建⽴⼀个能够⽀撑企业运营和管理的模型,⽽时标型对象就是模型中的关键概念
时标型对象是⽀撑运营体系关键流程的执⾏结果
时标型对象是过去某个时刻或时段发⽣的事实(Fact)。时标型对象记录的数据是决策者和管理者关注的信息,满⾜对责任的可追溯性。时标型对象记录的数据是为了满⾜财务和法律的需求,倘若缺失,会影响到参与⽅的权益和义务
它记录的数据是决策者和管理者关注的信息
“责任”的可追溯性
它是过去某个时刻或时段发⽣的事实,注重其关键属性的不可变
“事实”的不可变更性
特征
根据某个时刻产⽣的业务动作,去寻找与该业务动作具有因果关系的领域对象
该业务步骤是否核⼼步骤?
业务步骤是否产⽣值得记录的可追溯结果?
倘若缺失该结果,会影响运营或管理吗?
思考方向
识别方法
时标型(Moment-Interval)对象
代表参与到流程中的参与⽅/地点/物,⽤绿⾊表示
由于 PPT 对象明确地表达了⼈、组织、地点和物品,因此也可以相对容易地在业务需求描述中 寻找到这些概念
PPT(Party/Place/Thing)对象
在时标型对象与\tPPT 对象(通常是参与⽅)之间参与的⻆⾊,⽤⻩⾊表示
寻找⻆⾊对象,往往从时标型对象与 PPT 对象之间的关系着⼿。这些⻆⾊对象相当于是 PPT对象的具体实现,相当于PPT 对象参与到业务流程中所属的身份
⻆⾊(Role)对象
对 PPT 对象的⼀种补充描述,⽤蓝⾊表示
在已经确定了 PPT 对象的前提之下,我们只需要判断这些 PPT 对象是否还需要做进⼀步的说明。若需要,就引⼊对应的描述对象
描述(Description)对象
四⾊建模法代表的领域对象类型
1.⾸先以满⾜管理和运营的需要为前提,寻找需要追溯的事件
2.根据这些需要追溯,寻找⾜迹以及相应的时标型对象
3.寻找时标对象周围的参与⽅/地点/物品
4.从中抽象⻆⾊
5.把⼀些信息⽤描述对象补⾜
建模步骤
四色建模法
6W模型
组成场景的要素常常被称之为6W模型,即描写场景的过程必须包含Who,What,Why,Where,When与hoW这六个要素
在6W模型中,我将领域功能划分为三个层次,即业务价值、业务功能和业务实现,我将其称之为“职责的层次”
定义为“职责(Responsibility)”,才能够更好地体现它与角色之间的关系,即“角色履行了职责”
业务价值体现了职责存在的目的,即解释了该领域需求的Why。只有提供了该职责,这个场景对于参与角色才是有价值的
业务价值
为了满足业务价值,我们可以进一步剖析为了实现该价值需要哪些支撑功能,这些业务功能对应6W模型中的What
业务功能
我们对功能深入分析,就可以分析获得具体的业务实现。业务实现关注于如何去实现该业务价值,因而对应于hoW
业务实现
职责的层次
通过“标识” 与“类型”对实体进行区分
特征一:具有唯一的身份标识
状态随着时间发生变化。连贯的、可持续变化的
特征二:可变性特征
实体只保留必要的属性与行为
如果两个实体所有状态都一样,但如果标识不一样,就是两个不同实体
实体是领域中需要唯一标识的领域概念
特性
有时,实体具有明确的自然标识,可以通过对概念的建模来实现;有时,可能没有已存的自然标识,将由应用程序生成并分配一个合理的标识,并将其用于数据存储
在考虑实体身份时,首先考虑该实体所在问题空间是否已经存在唯一标识符,这些标识符被称为自然键
1 自然键作为唯一标识
当问题域中没有唯一标识时,我们需要决定标识生成策略并生成它。最常见的生成方式包括自增数值、全局唯一标识符(UUID、GUID等)以及字符串等
2 应用程序生成唯一标识
将唯一标识的生成委派给持久化机制是最简单的方案。我们从数据库获取的序列总是递增,结果总是唯一的
3 持久化存储生成唯一标识
通过集成上下文,可以从另一个限界上下文中获取唯一标识。但一般不会直接使用其他限界上下文的标识,而是需要将其翻译成本地限界上下文的概念
4 使用另一个限界上下文提供的唯一标识
实体唯一标识的生成既可以发生在对象创建的时候,也可以发生在持久化对象的时候
5 唯一标识生成时间
委派标识和领域中的实体标识没有任何关系,委派标识只是为了迎合 ORM 而创建的。 对于外界来说,我们最好将委派标识隐藏起来,因为委派标识并不是领域模型的一部分,将委派标识暴露给外界可能造成持久化漏洞
6 委派标识
在聚合边界内,我们可以将缩短后的标识作为实体的本地标识。而作为聚合根的实体需要全局的唯一标识
7 本地标识和全局标识
唯一标识
将相关行为委托给值对象和领域服务:实体专注于身份和连续性,如果将过多的职责添加到实体上,容易使实体变的臃肿。通常需要将相关行为委托给值对象和领域服务
值对象可合并、可比较和自验证,并方便测试。这些特征使其非常适用于承接实体的行为
1 将行为推入值对象
领域服务没有标识、没有状态,对逻辑进行封装。非常适合承接实体的行为
2 将行为推入领域服务
实体是业务操作的承载者,行为命名代表着很强的领域概念,需要使用通用语言中的动词,应极力避免无法表达业务逻辑的命名
3 重视行为命名
在实体行为成功执行之后,常常需要将变更通知给其他模块或系统,以触发后续流程。因此,需要向外发布领域事件
4 发布领域事件
跟踪变化最实用的方法是领域事件和事件存储。当命令操作执行完成后,系统发出领域事件。事件的订阅者可以接收发生在模型上的事件,在接收事件后,订阅方将事件保存在事件存储中
5 变化跟踪
实体行为
实体应该面向行为,这意味着实体应该公开领域行为,而不是公开状态。专注于实体行为非常重要,它使得领域模型更具表现力。通过对象的封装特性,其状态只能被封装它的实例进行操作,这意味着任何修改状态的行为都属于实体
关注行为,而非数据
建模模式有利于提升实体的表达性和可维护性
唯一标识是实体的身份,在完成分配后,绝对不允许修改
1 妥善处理唯一标识
Specification 也称规格模式,主要针对领域模型中的描述规格进行建模。规格 Specification 模式是将一段领域知识封装到一个单元中,称为规格。
数据检索:是从存储中获取数据,查找与规范匹配的记录
内存中验证:是指检查某个对象是否符合规格描述
创建新对象:(该场景非常罕见,我们暂且忽略)
主要有三种这样的场景
2 使用 Specification 进行规格建模
实体拥有自己的生命周期,往往会涉及状态管理。对状态建模是实体建模的重要部分。管理实体状态,状态设计模式具有很大的诱惑
3 使用 Enum 简化状态模式
4 使用业务方法和 DTO 替换 setter (JAVA)
实体存储的数据,往往需要读取出来,在 UI 中显示,或被其他系统使用。实体作为领域概念,不允许脱离领域层,而在 UI 中直接使用。此时,我们需要使用备忘录或 DTO 模式,将实体与数据解耦
5 使用备忘录或 DTO 处理数据显示
方法的副作用,是指一个方法的执行,如果在返回一个值之外还导致某些“状态”发生变化,则称该方法产生了副作用
Query 方法:有返回值,但不改变内部状态
Command 方法:没有返回值,但会改变内部状态
根据副作用概念,我们可以提取出两类方法
6 避免副作用方法
实体主要职责是维护业务不变性,当多个用户同时修改一个实体时,会将事情复杂化,从而导致业务规则的破坏。对此,需要在实体上使用乐观锁进行并发控制,保障只有一个用户更新成功,从而保护业务不变性
7 使用乐观锁进行并发控制
实体建模模式
实体是问题域中具有唯一身份的领域概念
与值对象不同,实体的相等性严格基于唯一标识
实体具有明确的生命周期
在实体生命周期中,需要严格遵从业务不变性条件
应该将实体定位为值对象的容器,把行为推到值对象和领域服务中,从而避免实体的臃肿
实体可以提供属性、对象、对象组等多种验证规则,从而保护业务
实体的唯一标识,可以来自领域概念、程序生成、存储生成等
规格模式是处理实体规则描述的一大利器
乐观锁的使用,将大大减少并发导致的业务错误
总结
实体(Entity)
值对象不具有身份标识,它纯粹用于描述实体的特性,用于度量和描述事物。值对象是不可变的、无副作用并且易于测试的
值对象用于度量和描述事物,我们可以非常容易的对值对象进行创建、测试、使用、优化和维护
一个值对象,或者更简单的说,值,是对一个不变的概念整体建立的模型。在这个模型中,值就真的只有一个值。和实体不一样,他没有唯一标识,而是通过封装属性的对比来决定相等性。一个值对象不是事物,而是用来描述、量化或测量实体的
当你关系某个对象的属性时,该对象便是一个值对象。为其添加有意义的属性,并赋予相应的行为。我们需要将值对象看成不变对象,不要给他任何身份标识,还应该尽量避免像实体对象一样的复杂性
即使一个领域概念必须建模成实体,在设计时也应该更偏向于将其作为值对象的容器
只是度量或描述领域中某件东西的一个概念
度量或描述
值对象在创建后,就不会发生改变,如果需要改变的话,将创建一个新的值对象并对原有对象进行替换
不变性
一个值对象可以只有一个属性,也可以拥有一组相关属性(一组属性联合起来表达一个整体上的概念)
概念整体性
值对象的构造函数应该用于保障概念整体性的有效性
有效性
如果需要改变的话,我们需要将整个值对象替换成一个新的值对象实例
可替换性
通过比较两个对象的类型和属性来决定其相等性
属性相等性
由于不变性,值对象的方法一般为一个无副作用函数,这个函数表示对某个对象的操作,它只用于产生输出,不会修改对象状态
方法无副作用
值对象的特征汇总
理解值对象
值对象是实体的状态,它描述与实体相关的概念
当一个概念缺乏明显的身份时,基本可以断定它大概率是一个值对象
1 表示描述性的、缺失身份的概念
领域驱动设计的一切都是为了明确传递业务规则和领域逻辑。像整数和字符串这样的技术单元并不适合这种情况
2 增强确定性
何时使用值对象
值对象是不可变的、无副作用并且易于测试的
缺失身份是值对象和实体最大的区别
由于值对象没有身份,且描述了领域中重要的概念,通常,我们会先定义实体,然后找出与实体相关的值对象。一般情况下,值对象需要实体提供上下文相关性
1 欠缺身份
如果实体具有相同的类型和标识,则会认为是相等的。相反,值对象要具有相同的值才会认为是相等的
2 基于属性的相等性
值对象应该尽可能多的暴露面向领域概念的行为
3 富含行为
通常情况下,值对象会内聚封装度量值和度量单位
当然,并不局限于此,对于拥有概念整体性的对象,都具有很强的内聚性
4 内聚
一旦创建完成后,值对象就永远不能改变
如果需要改变值对象,应该创建新的值对象,并由新的值对象替换旧值对象
5 不变性
对于用于度量的值对象,通常会有数值,此时,可以将其组合起来以创建新的值
6 可组合性
值对象作为一个概念整体,决不应该变成无效状态,它自身就应该负责对其进行验证
通常情况下,在创建一个值对象实例时,如果参数与业务规则不一致,则构造函数应该抛出异常
7 自验证性
不变性、内聚性和可组合性使值对象变的可测试
8 可测试性
实现值对象
通过一些常用的值对象建模模式,可以提高值对象的处理体验
静态工厂方法是更简单、更具有表达性的一种技巧
1 静态工厂方法
通过使用更具体的领域模型类型封装技术类型,使其更具表达能力
2 微类型
通常情况下,需要尽量避免使用值对象集合。这种表达方式无法正确的表达领域概念
使用值对象集合通常意味着需要使用某种形式来取出特定项,这就相当于为值对象添加了身份
3 避免集合
值对象建模模式
处理值对象最难的点就在他们的持久化。一般情况下,不会直接对其进行持久化,值对象会作为实体的属性,一并进行持久化处理
持久化过程即将对象序列化成文本格式或二进制格式,然后保存到计算机磁盘中
许多 NoSQL 数据库都使用了数据反规范化,为我们提供了很大便利
1 NoSQL
在 SQL 数据库中存储值对象,可以遵循标准的 SQL 约定,也可以使用范模式
基本思路就是将值对象与其所在的实体对象保存在同一张表中,值对象的每个属性保存为一列
这种方式,是最常见的值对象序列化方式,也是冲突最小的方式,可以在查询中使用连接语句进行查询
2.1 多列存储单个值对象
值对象的所有属性保存为一列。当不希望在查询中使用额外语句来连接他们时,这是一个很好的选择
1 创建持久化格式
2 在保存时进行数据转换
3 在加载时解析值
一般情况下,会涉及以下几个操作
2.2 单列存储单个值对象
这种应用是前种方案的扩展。将整个集合序列化成某种形式的文本,然后将该文本保存到单个数据库列中
1 列宽。数据库列的长度不好确定
2 不方便查询。由于值对象集合被序列化到扁平化文本中,值对象的属性不能使用 SQL 进行查询
3 需要自定义类型。持久化框架对该类型的映射没有提供支撑,需要对其进行扩展
需要考虑的问题
2.3 多个值对象序列化到单个列中
我们应该首先考虑将领域概念建模成值对象,而不是实体
我们可以使用委派主键的方式,使用两层的层超类型。在上层隐藏委派主键。这样我们可以自由的将其映射成数据库实体,同时在领域模型中将其建模成值对象
2.4 使用数据库实体保存多个值对象
大多持久化框架都提供了对枚举类型的支持。要么使用枚举值得 String,要么使用枚举值得 Index,其实都不是最佳方案,对以后得重构不太友好,建议使用自定义 code 进行持久化处理
2.5 ORM 与 枚举状态对象
在使用 DB 进行值对象持久化时,经常遇到阻抗
1 根据领域模型来来设计数据模型,而不是通过数据模型来设计领域模型
2 报表和商业智能应该由专门的数据模型进行处理,而不是生产环境的数据模型
当面临阻抗时,我们应该从领域模型角度,而不是持久化角度去思考问题
2.6 阻抗
2 SQL
持久化
标准类型是用于表示事物类型的描述性对象
1 用值对象表示标准类型
当模型概念从上游上下文流入下游上下文中,尽量使用值对象来表示这些概念。在有可能的情况下,使用值对象完成上下文之间的集成
2 最小集成
值对象其他用途
值对象是 DDD 建模结构体,它用于表示像度量这样的描述概念
值对象没有身份,比实体要简单得多
建议将数字和字符串封装成值对象,以更好的表示领域概念
值对象是不可变的,他们的值在创建后,就不在发生变化
值对象是内聚的,将多个特征封装成一个完整的概念
可以通过组合值对象来创建新的值对象,而不改变原始值
值对象是自验证的,它不应该处于无效状态
可以使用静态工厂、微类型等模式提高值对象的易用性
对于 NoSQL 的存储,直接使用反规范持久化值对象,面向文档数据库是首选
对于 SQL 存储,相对要麻烦下,存在大量的阻抗
值对象(Value Object)
聚合是一种边界,它可以封装一到多个实体与值对象,并维持该边界范围之内的业务完整性。
在聚合中,至少包含一个实体,且只有实体才能作为聚合根(Aggregate Root)。
在领域驱动设计中,没有任何一个类是单独的聚合,因为聚合代表的是边界概念,而非领域概念。
事务一致性
最终一致性
业务规则总是保持一致
在一致性边界之内建模真正的不变条件
设计小聚合
通过唯一标识引用其他聚合
建模原则
聚合(Aggregate)
应用服务(Application Service)[待续]
领域服务(Domain Service)[待续]
领域事件(Domain Event)[待续]
资源库(Repository)[待续]
工厂(Factory)[待续]
战术层面主要要素
若在工作坊过程中将主要的精力用于寻找业务流程中产生的领域事件,则这个过程可以认为是宏观级别的事件风暴,其目的是探索业务全景(Big Picture Exploration)
在识别出全景事件流之后,就可以标记时间轴的关键时间点作为划分领域边界和限界上下文边界的依据;同时也可以基于事件表达的业务概念对领域进行划分,最终确定候选的子领域和限界上下文
事件是事件风暴的主要驱动力,寻找出来的事件则是领域分析模型的骨架
在识别事件的过程中,工作坊的参与人员应尽可能站在管理和运营的角度去思考领域事件
事件(Event):通常用橙色即时贴表示
热点(Hot Spot):需要提醒业务人员或技术人员特别注意的热点事件,通常粉红色即时贴来表达该警告信息
由用户活动触发:标记参与事件的用户角色,用黄色即时贴绘制火柴棍人表示
当条件满足时:标记引起事件的策略,用紫色即时贴表示
外部系统:标记引起事件的外部系统,用浅粉色即时贴表示
触发事件的起因
事件
使用事件风暴探索业务全景
探索业务全景
通过探索业务全景获得的事件流,围绕着事件获得领域分析模型。这些领域分析建模要素除了领域事件之外,还包括决策命令、读模型和聚合
事件风暴的领域分析建模方法通常会以业务全景探索的结果作为领域分析建模的基础
使用浅蓝色即时贴表示
通观事件之起因,除了外部系统是直接发布事件之外,无论是用户活动,还是满足某个条件,都需要一个命令(Command)来响应,它才是直接导致事件发生的“因”
决策命令往往由动宾短语组成,例如Place Order、Send Invitation等
决策命令在事件风暴中具有较高的重要性,它是领域分析建模的一个重要驱动力,因为通过它连接了用户、策略、聚合、读模型和事件
必须基于足够充分的信息才能做出正确的决策,提供这些信息的对象被称之为读模型(Read Model)
通常用浅绿色即时贴表示
信息
根据业务规则,当某个条件满足时,会触发一个决策命令,这个业务规则被命名为策略(Policy)
通常用紫色标签表示
策略
数据支撑
决策命令(Decision Command)
读模型:当决策命令由用户引发时,可以确认该决策命令的发生是否需要提供足够的读模型信息。读模型是用户通过查询(读)操作获得的
策略:就表示事件发生后某些数据满足了某条业务规则
读模型和策略
分析模型要素
运用事件风暴进行领域分析建模
领域分析建模
事件风暴
精炼领域分析模型[待续]
将模型清晰地分解成操作级和知识级
如果某个类型拥有多种相似的关联,可以为这些关联对象定义⼀个新的类型,并建⽴⼀个知识级类型来区分它们
保证分析模型中的概念遵循单⼀抽象层次原则
选择(Selection):需要基于某些条件(Criterion)选择对象的⼀个⼦集,且需要多次刷新其选择
验证(Validation):需要根据确定的⽬标获得满⾜条件的合适对象
按需构造(Construction-to-order):需要描述对象应该做什么⽽⽆需解释对象执⾏的细节, 这样就可以构造⼀个候选对象来满⾜需求
规格模式
分析模式
DDD领域驱动设计 [By Lucas]
收藏
0 条评论
回复 删除
下一页