实现领域驱动设计-笔记
实现领域驱动设计-笔记
关联
www.VaughnVernon.com
推特 @VaughnVernon
本书
前提
领域模型一些概念。
战略设计,通用语言,限界上下文,
架构,六边形架构,rest,事件驱动,适配器
战术设计,聚合,实体,值对象,资源库,领域服务,领域事件,模块
书章节安排
阅读建议
内容
第一章 DDD入门
名词
领域专家:
统一建模语言UML(Unified Modeling Language)
核心域:最重要的、复杂的业务场景。
支撑子域:相对核心域次要的。
DDD的好处
帮助开发人员面向业务。
为什么需要DDD
梳理业务价值,集中业务知识;提升团队沟通、凝聚力;提升软件伸缩性;简化领域复杂性
梳理业务价值
处理领域复杂性
系统复杂性评分
贫血对象
DDD做了什么
- 将领域专家和开发人员聚集到一起。
- 所开发的软件能够反映出领域专家的思维模型。不是将精力花在了对“真实世界”的建模上,而是交付最具业务价值的软件。
- 领域专家和开发人员一起创建适用于领域建模的通用语言。有助于沟通,有助于促使原本存在分歧的领域专家达成一致。团队更有凝聚力。
- DDD关注业务战略
- 战术设计建模后,满足了软件设计的技术需求。DDD的最佳时间包含了高层的架构性实践和底层设计实践。关注业务规则和数据不变形。技术优点:可测试,伸缩性强,允许分布式计算。
如何DDD
通用语言和限界上下文共同构成了DDD的两大支柱。
步骤:建立通用语言,建立限界上下文
通用语言
介绍
- 同一个限界上下文中使用的语言;不同限界上下文中同一词语含义可能不同
- 团队中使用相同的通用语言。
- 通用语言是关于如何思考和运作业务本身的。
- 通用语言是持续演化改变成长壮大的。
- 领域专家和开发人员共同创建。
- 通用语言最初来自领域专家,他们最了解业务,并且深受工业标准的影响。
- 不同领域专家会在概念和术语上产生分歧,甚至犯错。
- 团队成员对于通用语言有时会做出妥协,妥协的是概念、术语、定义,而不是质量。质量?
通用语言建模&根据建模编写的代码案例
patient 病人
Shot 注射
flu 流感
dose 一剂
Nurse 护士
Vaccine 疫苗
Adult 成人
| 业务描述 | 代码 |
|---|---|
| “谁管呢?写代码就行了” | patient.setShotType(ShotTypes.TYPE_FLU); patient.setDose (dose); patient.setNurse (nurse); |
| 我们给病人注射流感疫苗 | patient.giveFluShot(); |
| 护士给病人打了标准剂量的流感疫苗 | Vaccine vaccine = vaccines.standardAdultFluDose (); nurse.administerFluVaccine (patient, vaccine); |
如何建立通用语言
最初来自领域专家。
创建一个包含简单定义的术语表,或者文档。罗列术语,包括好的和不好的,并注明好与不好的原因。
找团队其他人员检查成果。对于分歧做一些准备。
面对分歧。
- 团队成员讨论
- 查阅参考资料,词典
- 引用标准
绘制物理模型图和概念模型图,并标注名字和行为。
经过一段时间,通用语言文档将过时,只有团队交流和代码持续到最后。慢慢抛弃模型图、术语表和文档。
通用语言的局限性
使用DDD的业务价值-详细-原书总结+自己的观点
- 区分核心域、支撑子域,来帮助区分业务重要与次要,进而帮助公司把重点放在重要的业务上。
- 通过业务模型完善,明确业务模型内细节,掌握更多业务价值。
- 让各个领域专家就分歧达成一致;
- 让掌握业务的人数更多;
- 将分散在各个业务人员上的业务知识集中到领域模型、软件中。
- 更贴合用户,减少使用系统培训。
- 项目人员对上下文边界、关系明晰的过程可以了解企业架构。
- 辅助持续迭代业务。敏捷可应用到DDD中,持续迭代领域模型,迭代业务。
团队中DDD的使用
在 单个 限界 上下 文中 团队 成员 共享 一套 通用 语言。
不同 的 团队 有时 各自 负责 一个 限界 上下文,
团队之间对接限界上下文
一个上下文中,使用战术建模工具: 聚合( Aggregate, 10)、 实体( Entity, 5)、 值 对象( Value Object, 6)、 领域 服务( Domain Service, 7)和领域 事件( Domain Event, 8) 等。
第二章
建模:列出建模元素,分组,找出语言边界
第三章 上下文映射图
介绍:业务方面:属于解决方案空间的一部分,帮助梳理清楚一个解决方案对外界的依赖。
用途:可以让团队成员知道自己做的内容范围。
上下文之间的映射关系种类:
客户方供应方
遵奉者
另谋他路,即无关系
大泥球
共享内核
团队关系:合作关系
限界上下文接口之间:防腐层,开放主机服务(http),发布语言(json,xml)。
上下文映射图首先用来表示当前的项目状态。边界和关系。
不是系统拓扑图,不是企业架构。
图中的元素:
U:上游
D:下游
建议:上下文映射图贴在项目组墙上;放在wiki中并不会去看。
为不同限界上下文中的Dsicusion采用强类型?
第四章 架构
DDD无需绑定特定的架构。
核心域位于限界上下中,可以采用多种架构。
架构风格受到功能需求的限制。
DDD无需结合分层架构。
一个项目无需采用所有的架构。
名词
- 架构风格:阐述如何实现某种架构
- 应用架构:
- 架构模式:关注一种架构的某个方面,比设计模式更宽泛。
- 六边形架构:端口和适配器。 也称Onion架构。
- defect:缺陷
项目回顾
- 因为桌面应用程序的应用层+中央数据库;采用了分层架构中的客户端-服务器风格。
- 募集了充足资金后,在开发敏捷项目管理系统前,先开发了协作工具。协作工具的好处:加入了火热的协作工具市场,为敏捷项目提供了附加功能(提高敏捷+协作提供支付能力)。
- 增加了单元测试和功能测试保证软件质量。在分层架构中使用了依赖倒置,方便的替换UI层和基础设施层。
- 开始盈利,增加移动端;软件使用REST,提供联合身份验证,项目和时间管理工具。
- 转向六边形架构来应对新需求,增加NoSQL,增加消息机制。
- 每个月新增几百个租户,添加了一个服务将协作工具中的遗留数据迁移到云上。
- 增加功能,如:TeackOvation,defect。客户要求分布式处理功能,引入CRES架构模式。使用了Pipe and Filter(管道和过滤器)完成CRES。
- 被RoaringCloud收购,突增了大量的订阅用户;将管道和过滤器分部化和并行化,加入长时处理过程和Sagas(分布式事务)
依赖注入&六边形架构对分层架构的改进
REST todo
六边形架构对SOA和REST的支持 todo
cqrs架构
介绍
命令与查询分离。修改不返回数据,只有查询返回数据
用途
解决数据显示复杂性
- 缺点:编写更多的代码
- 优点:查询与命令隔离的优点:查询与命令占用资源不同,可以优化查询。
实践
mapper的查询与增删改分离;
聚合的查询也与增删改分离命令(写)模型:增删改+通过ID查询 查询(读)模型:其他查询
cqrs各个方面的联系
关系图

查询模型不反映领域行为,只用于数据显示。视图对于查询模型有帮助
客户端驱动命令
客户端向服务器发送命令,服务器在聚合上执行相应的行为。
用户界面需要:1.收集足够的信息;2.演绎式的引导用户正确提交命令。
命令处理器
- 风格种类:
分类。一个类,多个方法。
专属;一个类,一个方法简单,易于理解
消息风格;职责单一,处理器直接独立。
一个处理器不应该依赖另一个处理器异步。复杂。伸缩性强
使用事件源时,修改Java内存中的聚合实例后,发布领域事件。然后消费领域事件,持久化聚合实例。
发布领域事件,可以用sping的事件或mq。
事件源与cqrs一块? 事件日志包含在业务需求。
命令修改对查询模型的影响
同步。
- 方式:需要在一个事务中修改,命令和查询的存储位置需要一致(同一个数据库,同一缓存)
- 优点:用户看到的东西一致;
- 缺点:长事务耗费系统资源,命令和查询的存储位置需要一致。
异步。
方式:@Async;MQ
缺点:更新延迟时间不可预测。MQ提升系统复杂性
优点:
- 满足其他的服务层协议。
- 利用MQ存储历史事件,则可以回放事件。
异步数据一致性:
- 前端提前展示未来修改后的内容。
- 展示数据的来源时间,让用户自己判断数据的实时性,用户可刷新。
- 通知用户请求已经正在处理,全部处理完还需时间。
事件驱动架构
介绍
处理事件的生成,发现,处理的软件架构。
六边形架构,遵循AMQP,不同于http的接收返回
事件驱动结合六边形架构:输入事件,输出事件,不同接口
事件类型:
- 领域事件
- 日志事件
- 系统监控事件
事件驱动架构带来的问题:
长时处理过程,过程如何开始?如何分布在整个企业范围内?如何跟踪处理进度?管道和过滤器
Linux命令示例
1
2在 phone_ numbers. txt 文件 中统 计 含有 电话 区号“ 303” 的 所有 文本 行的 数量。
cat phone_numbers.txt | grep 303 | wc -l # cat:向标准输出流输出文本内容 |:管道; grep 303:过滤得到含有 电话 区号“ 303”的文本行; wc -l:统计文本行数量示例转为Java事件驱动

长时处理过程 saga
长时并行处理流程图
- 三种设计方法
1.组合任务,每一步持久化
2.一组聚合?
3.无状态,每一步都对消息扩充
引入的问题:无法知道两个不同流程的事件是同一个处理过程。需引入事件唯一标识
事件源
作用
- 跟踪领域对象的修改;
- 审计日志;
- 方便调试;
- 方便对系统bug进行补偿;
- 辅助业务做:如果……会怎么样?
与事件源类似的东西,例如:git跟踪对文件的修改
实现原理
每次操作,至少发布一个领域事件,每个领域事件都进行存储。
如何消除成百上千事件重放对系统的压力:每隔50-100个事件进行一次聚合的快照存储。
查询模型的数据源可以在事件存储更新之后得到静默更新?
数据网织和基于网格的分布式计算
数据网织定义?聚合是基于图的缓存中的值部分
存
分布式缓存可以容易的对领域模型进行持久化。
1 |
|
高性能,高可用
多节点部署.
缓存冗余性的工作机制: 这里 有一个 例子: 其中 一个 节点 作为 主 缓存, 其他 节点 作为 二级 缓存。 如果 主 缓存 失效, 其中 一个 二级 缓存 将会 成为 新的 主 缓存。 当先 前 失效 的 主 缓存 恢复 之后, 新 主 缓存 中的 数据 将被 复制 到 恢复 后的 缓存 中, 此时 该 恢复 后的 缓存 将 变为 二级 缓存。
结合领域事件?
大多数数据网 织可 以对 缓存 层面 和 入口 层面 上 所发 生的 操作 自动 地 发出 事件 通知。 这些 事件 不应该 和 领域 事件 产生 混淆。
从 聚合 中直 接 发布 领域 事件, 领域 事件 可能 需要 继承 框架 中的 某种 事件 类型, 比如 GemFire 中的 EntryEvent, 但是 相比 起 这些 领域 事件 所 提供 的 强大 功能 来说, 这种 继承 只是 很小 的 代价。继承有什么代价?谁继承谁?
如何 在数据网织 中 使用 领域 事件 呢? 聚合使用一个简单的DomainEventPublisher组件。对于数据网织的缓存来说,这个发布组件只是将事件放在某个特定的缓存中。
此后,数据网织将缓存事件通过同步或异步的方式发送给订阅方。为了不浪费内存,可以在得到所有订阅方的应答后,将缓存事件从缓存图移除。
如何支持持续查询
查询模型在领域事件存储后静默更新?
前端对可能发生的更改进行监听。
第五章 实体
为什么使用实体
名词
实体:
作用:衡量一个对象的个性特征,区分不同对象,找到相同对象。
同一个实体对象:一段时间内持续变化的对象拥有相同的唯一标识。
实体对象和值对象的区别:实体对象又唯一的身份标识和可变性特征。
有时,值对象比实体对象更适合建模。DDD并不总能满足我们的业务需求。
我们 通过 标识 对对 象 进行 区分, 而 不是 属性, 此时 我们 应该 将 标识 作为 主要 的 模型 定义。 同时 我们 需要 保持 简单 的 类 定义, 并且 关注 对象 在 其生 命 周期 中的 连续性 和 唯一 标识 性。 我们 不应该 通过 对象 的 状态 形式 和 历史 来 区分 不同 的 实体 对象…… 对于 什么 是 相同 的 东西, 模型 应该 给出 定义。?
如何生成实体唯一标识
实体 设计 早期, 我们将 刻意 地 把 关注 点 放在能 体现 实体 身份 唯一 性的 主要 属性 和 行为 上, 同时 还将 关注 如何 对 实体 进行 查询。 另外, 我们 还会 刻意 地 忽略 掉 那些 次要 的 属性 和 行为。
在 设计 实体 时, 我们 首先 需要 考虑 实体 的 本质 特征, 特别是 实体 的 唯一 标识 和 对 实体 的 查找, 而 不是 一 开始 便 关注 实体 的 属性 和 行为。 只有 在 对 实体 的 本质 特征 有用 的 情况下, 才 加入 相应 的 属性 和 行为。
值 对象 可以 用于 存放 实体 的 唯一 标识。 值 对象 是 不变( immutable) 的, 这可 以 保证 实体 身份 的 稳定性, 并且 与 身份 标识 相关 的 行为 也可以 得到 集中 处理。 这样, 我们 便 可以避免 将 身份 标识 相关 的 行为 泄漏 到 模型 的 其他 部分 或者 客户 端 中。?
创建 实体 身份 标识 的 策略
- 用户提供一个或多个初始唯一值作为程序输入,程序保证这些初始值是唯一的。
- 程序内部通过某种算法生成身份标识
- 依赖持久化存储,比如数据库来生成唯一标识。
- 另一个限界上下文已经决定了唯一标识,这作为程序输入。
对创建唯一标识策略副作用的衡量
将 关系 型 数据库 用于 对象 持久 化时,泄漏到领域模型。
标识 生成 的 时间、
关系 型 数据 的 引用 标识
ORM 在 标识 创建 过程 中的 作用,
唯一标识的稳定性。用户提供唯一标识策略
直接,看起来简单。
复杂性:
- 需要用户生成高质量的标识,标识唯一,不可变。
程序生成唯一标识策略
如果 应用 程序 处于 集群 环境 或者 分布 在 不同 的 计算 节点 中需要注意。
衡量唯一标识:可读性,递增性,严格递增性,标识生成与实体生成的早晚方法一
- 计算节点的当前时间,以毫秒记
- 计算节点的IP地址
- 虚拟机中工厂对象实例的对象标识
- 虚拟机中由同一个随机数生成器生成的随机数
方法二
java. util. UUID。方法三
自己实现,mysql+redis标识的维护
用值对象维护更好数据库生成唯一标识策略
其他限界上下文提供唯一标识策略
如何从实体设计中捕获通用语言
实体本质特征
实体:
关注数据库,表,列,和对象映射,得到的是贫血模型。
通用语言:
名词:概念
形容词:描述概念
动词:可以完成的操作
简单的通用语言:术语+用例
通用语言不只包括术语和用例。
通用语言应该直接反映在代码中。
一个领域场景今后还要使用,可以用轻量化的文档记录下来。
领域模型:不只是组类和类上定义的操作。
如何表达实体的角色和职责
如何对实体进行验证和持久化
其他知识点
系统复杂性
- dto和dto组装器会增加系统复杂性。