目 录 第Ⅰ部分 领域驱动设计的原则与实践第1章 什么是领域驱动设计 31.1 为复杂问题域创建软件的挑战 41.1.1 未使用通用语言创建的代码 41.1.2 组织结构的缺乏 51.1.3 泥球模式将扼杀开发 51.1.4 缺乏对问题域的关注 51.2 领域驱动设计模式如何管理复杂性 61.2.1 DDD的战略模式 61.2.2 DDD的战术模式 81.2.3 问题空间与解空间 91.3 领域驱动设计的实践与原则 101.3.1 专注于核心领域 101.3.2 通过协作进行学习 101.3.3 通过探索和实验来创建模型 101.3.4 通信 111.3.5 理解模型的适用性 111.3.6 让模型持续发展 111.4 领域驱动设计的常见误区 121.4.1 战术模式是DDD的关键 121.4.2 DDD是一套框架 121.4.3 DDD是一颗灵丹妙药 121.5 要点 13第2章 提炼问题域 152.1 知识提炼与协作 152.1.1 通过通用语言达成共识 162.1.2 领域知识的重要性 172.1.3 业务分析员的角色 172.1.4 一个持续过程 172.2 与领域专家一起获得领域见解 182.2.1 领域专家与业务相关人员的对比 182.2.2 对于业务的更深刻理解 182.2.3 与你的领域专家互动 192.3 有效提炼知识的模式 192.3.1 专注在最有意思的对话上 192.3.2 从用例开始 192.3.3 提出有力的问题 202.3.4 草图 202.3.5 CRC卡 212.3.6 延迟对模型中概念的命名 212.3.7 行为驱动开发 212.3.8 快速成型 232.3.9 查看基于纸面的系统 232.4 查看现有模型 232.4.1 理解意图 242.4.2 事件风暴 242.4.3 影响地图 252.4.4 理解业务模型 262.4.5 刻意发现 272.4.6 模型探讨漩涡 272.5 要点 28第3章 专注于核心领域 313.1 为何要分解一个问题域 313.2 如何捕获问题的实质 323.2.1 超越需求 323.2.2 为达成什么是核心内容的共识而捕获领域愿景 323.3 如何专注于核心问题 333.3.1 提炼问题域 343.3.2 核心领域 353.3.3 将你的核心领域当作一款产品而非一个项目 363.3.4 通用域 363.3.5 支撑域 373.4 子域如何决定解决方案的形成 373.5 并非一个系统的所有部分都会经过良好设计 383.5.1 专注于清晰边界而非完美模型 383.5.2 一开始核心领域不必总是需要是完美的 393.5.3 构建用于替代而非重用的子域 393.6 如果没有核心领域怎么办 393.7 要点 39第4章 模型驱动设计 414.1 什么是领域模型 414.1.1 领域与领域模型的对比 424.1.2 分析模型 434.1.3 代码模型 434.1.4 代码模型是领域模型的主要表现 444.2 模型驱动设计 444.2.1 预先设计的挑战 444.2.2 团队建模 464.3 使用通用语言将分析和代码模型绑定在一起 474.3.1 语言的生存周期将大于软件 484.3.2 业务语言 484.3.3 开发人员和业务之间的转译 484.4 基于通用语言进行协作 494.4.1 通过使用具体示例来定制出语言 504.4.2 教导你的领域专家专注在问题上而不要跳到解决方案 504.4.3 塑造语言的最佳实践 514.5 如何创建有效的领域模型 524.5.1 不要让实情妨碍一个好模型 524.5.2 仅对相关内容建模 534.5.3 领域模型都是暂时有用的 534.5.4 要十分清楚专业术语 544.5.5 限制你的抽象 544.6 何时应用模型驱动设计 554.6.1 如果它不值得花费精力,则不要尝试对其建模 564.6.2 专注于核心领域 564.7 要点 56第5章 领域模型实现模式 595.1 领域层 595.2 领域模型实现模式 605.2.1 领域模型 615.2.2 事务脚本 645.2.3 表模块 675.2.4 活动记录 675.2.5 贫血领域模型 675.2.6 贫血领域模型和函数编程 685.3 要点 71第6章 使用有界上下文维护领域模型的完整性 736.1 单个模型的挑战 746.1.1 模型的复杂性可能会增加 746.1.2 多个团队处理单个模型 746.1.3 模型语言中的歧义 756.1.4 领域概念的适用范围 766.1.5 集成遗留代码或第三方代码 786.1.6 领域模型并非企业模型 786.2 使用有界上下文划分和破除大模型 796.2.1 定义模型的边界 816.2.2 子域和有界上下文之间的差异 846.3 实现有界上下文 856.4 要点 88第7章 上下文映射 917.1 一个现实情况的映射 927.1.1 技术的现实 927.1.2 组织的现实 937.1.3 映射一个相关现实情况 947.1.4 用X标记核心领域的位置 947.2 认识有界上下文之间的关系 947.2.1 防止损坏层 957.2.2 共享内核 967.2.3 开放宿主服务 967.2.4 分道扬镳 977.2.5 合作关系 987.2.6 一种上游/下游关系 987.3 传递上下文映射 997.4 上下文映射的战略重要性 1007.4.1 保持完整性 1007.4.2 解决计划的基础 1017.4.3 理解所有权和职责 1017.4.4 揭示业务工作流中的混乱区域 1017.4.5 识别非技术障碍 1017.4.6 鼓励良好的沟通 1017.4.7 帮助加入的新员工 1027.5 要点 102第8章 应用程序架构 1038.1 应用程序架构 1038.1.1 分离应用程序的问题 1038.1.2 从领域的复杂性中进行抽象 1048.1.3 分层架构 1048.1.4 依赖倒置 1058.1.5 领域层 1058.1.6 应用程序服务层 1058.1.7 基础架构层 1068.1.8 跨层通信 1068.1.9 隔离测试 1078.1.10 不要在有界上下文之间共享数据结构 1088.1.11 应用程序架构与用于有界上下文的架构的对比 1098.2 应用程序服务 1108.2.1 应用程序逻辑与领域逻辑的对比 1118.2.2 定义和公开能力 1128.2.3 业务用例协作 1128.2.4 应用程序服务表示的是用例,而不是创建、读取、更新和删除 1128.2.5 作为实现详情的领域层 1138.2.6 领域报告 1138.2.7 读取模型与事务模型的对比 1138.3 应用程序客户端 1158.4 要点 117第9章 团队开始应用领域驱动设计通常会遇到的问题 1199.1 过分强调战术模式的重要性 1209.1.1 将相同架构用于所有的有界上下文 1209.1.2 力求战术模式尽善尽美 1209.1.3 错误估计构造块对于DDD的价值 1209.1.4 专注于代码而非DDD的原则 1219.2 缺失了DDD的真实价值:协作、通信和上下文 1219.2.1 由于低估上下文的重要性而产生大泥球 1229.2.2 未能成功创建UL将造成歧义和误解 1229.2.3 由于缺乏协作将只能设计专注于技术的解决方案 1239.3 在不重要的部分花费太多时间 1239.4 简单问题复杂化 1239.4.1 将DDD原则应用到具有少量业务预期的琐碎领域 1249.4.2 别将CRUD作为反模式 1249.4.3 将领域模型模式用于每一个有界上下文 1249.4.4 问一问自己:额外的复杂性是否值得 1249.5 低估应用DDD的成本 1259.5.1 尝试在没有积极专注的团队的情况下取得成功 1259.5.2 项目背后没有领域专家时的协作尝试 1259.5.3 在非迭代式开发方法中进行学习 1259.5.4 将DDD应用到每一个问题 1269.5.5 为不必要的纯粹性而牺牲实用主义 1269.5.6 寻求验证会浪费精力 1269.5.7 永远力求代码之美 1279.5.8 DDD关乎的是提供价值 1279.6 要点 127第10章 应用DDD的原则、实践与模式 12910.1 推广使用DDD 12910.1.1 培训团队 13010.1.2 与业务人员进行交流 13010.2 应用DDD的原则 13110.2.1 理解愿景 13110.2.2 捕获所需的行为 13110.2.3 理解环境的现实情况 13210.2.4 对解决方案建模 13310.3 探究和实验 13910.3.1 质疑假设 13910.3.2 建模是一项持续性活动 13910.3.3 不存在错误的模型 14010.3.4 灵活的代码有助于探索发现 14010.4 让隐式内容变得显式 14010.4.1 处理歧义 14110.4.2 为事物命名 14310.5 问题解决人先行,技术专家后行 14310.6 如何才能知道我在正确地工作 14310.6.1 好用就足够了 14410.6.2 实践、实践、实践 14410.7 要点 144第Ⅱ部分 战略模式:在有界上下文之间通信第11章 有界上下文集成介绍 14911.1 如何集成有界上下文 15011.1.1 有界上下文是独立自主的 15011.1.2 在代码层面集成有界上下文的挑战 15111.1.3 使用物理边界来强制实现整洁的模型 15411.1.4 集成遗留系统 15511.2 集成分布式有界上下文 15811.2.1 集成用于分布式有界上下文的策略 15911.2.2 数据库集成 15911.2.3 平面文件集成 16011.2.4 RPC 16111.2.5 消息传递 16211.2.6 REST 16211.3 DDD使用分布式系统的挑战 16211.4 分布式事务将损害可扩展性和可靠性 16511.4.1 有界上下文不必彼此保持一致 16611.4.2 最终一致性 16611.5 事件驱动响应式DDD 16711.5.1 展示响应式解决方案的弹性和可扩展性 16811.5.2 异步消息传递的挑战和取舍 16911.5.3 RPC还有价值吗 16911.6 SOA和响应式DDD 17011.6.1 将你的有界上下文视作SOA服务 17111.6.2 进一步处理微服务架构 17411.7 要点 175第12章 通过消息传递集成 17712.1 消息传递基础 17812.1.1 消息总线 17812.1.2 可靠的消息传递 18012.1.3 存储转发 18012.1.4 命令和事件 18012.1.5 最终一致性 18112.2 使用NServiceBus构建一个电子商务应用程序 18212.2.1 系统设计 18312.2.2 从Web应用程序发送命令 18712.2.3 处理命令和发布事件 19612.2.4 使用消息传递网关让外部HTTP调用变得可靠 20312.2.5 实践中的最终一致性 21112.2.6 有界上下文会存储其本地所需的所有数据 21212.2.7 把所有内容都放在UI中 22012.3 维护消息传递应用程序 22312.3.1 消息版本管理 22312.3.2 监控和扩展 22812.4 将有界上下文与公共传输集成 23112.4.1 消息传递桥 23212.4.2 公共传输 23312.5 要点 240第13章 通过使用RPC和REST的HTTP来集成 24113.1 为何选用HTTP 24213.1.1 没有平台耦合 24313.1.2 每个人都理解HTTP 24313.1.3 大量的成熟工具和库 24313.1.4 内部测试你的API 24313.2 RPC 24413.2.1 在HTTP上实现RPC 24413.2.2 选择一种RPC风格 25913.3 REST 26013.3.1 深入浅出地解释REST 26013.3.2 用于有界上下文集成的REST 26313.3.3 维护REST应用程序 29713.3.4 将REST用于有界上下文集成的缺点 29813.4 要点 299第Ⅲ部分 战术模式:创建有效的领域模型第14章 构造块领域建模介绍 30314.1 战术模式 30414.2 对领域建模的模式 30514.2.1 实体 30514.2.2 值对象 30814.2.3 领域服务 31014.2.4 模块 31214.3 生命周期模式 31214.3.1 聚合 31214.3.2 工厂 31614.3.3 存储库 31614.4 显露模式 31714.4.1 领域事件 31714.4.2 事件溯源 31914.5 要点 320第15章 值对象 32315.1 何时使用值对象 32415.1.1 表示描述性的、欠缺身份的概念 32415.1.2 增强明确性 32515.2 定义特征 32715.2.1 欠缺身份 32715.2.2 基于特性的相等性 32715.2.3 富含行为 33115.2.4 内聚 33115.2.5 不可变 33115.2.6 可组合性 33315.2.7 自验证 33515.2.8 可测试 33815.3 常见的建模模式 33915.3.1 静态工厂方法 33915.3.2 微类型 34115.3.3 规避集合 34315.4 持久化 34615.4.1 NoSQL 34615.4.2 SQL 34715.5 要点 354第16章 实体 35516.1 理解实体 35616.1.1 具有身份和连贯性的领域概念 35616.1.2 上下文依赖 35616.2 实现实体 35716.2.1 分配标识符 35716.2.2 将行为推入到值对象和领域服务中 36316.2.3 验证并强制不变性 36516.2.4 专注于行为,而非数据 36816.2.5 避免“建模现实世界”的谬误 37116.2.6 分布式设计 37116.3 常见的实体建模原则和模式 37316.3.1 使用规范实现验证和不变条件 37316.3.2 避免状态模式;使用显式建模 37616.3.3 避免将接收器和设置器与备忘录模式结合使用 37916.3.4 选用无隐藏意外影响的功能 38016.4 要点 382第17章 领域服务 38317.1 理解领域服务 38417.1.1 何时使用领域服务 38417.1.2 领域服务解析 38817.1.3 避免使用贫血领域模型 38917.1.4 与应用程序服务对比 39017.2 利用领域服务 39017.2.1 服务层中 39017.2.2 领域中 39117.3 要点 397第18章 领域事件 39918.1 领域事件模式的实质 40018.1.1 已经发生了的重要领域事件 40018.1.2 响应事件 40118.1.3 可选的异步性 40118.1.4 内部事件与外部事件对比 40218.2 事件处理操作 40318.2.1 调用领域逻辑 40318.2.2 调用应用程序逻辑 40418.3 领域事件的实现模式 40418.3.1 使用.Net框架的事件模型 40418.3.2 使用内存中的总线 40618.3.3 Udi Dahan的DomainEvents静态类 40918.3.4 返回领域事件 41218.3.5 使用IoC容器作为事件分发器 41518.4 测试领域事件 41618.4.1 单元测试 41618.4.2 应用服务层测试 41718.5 要点 419第19章 聚合 42119.1 管理复杂对象图形 42219.1.1 选用单一遍历方向 42219.1.2 合格的关联关系 42419.1.3 选用ID而不是对象引用 42519.2 聚合 42819.2.1 围绕领域不变条件进行设计 42919.2.2 高层次的领域抽象 42919.2.3 一致性边界 42919.2.4 选用较小的聚合 43419.3 定义聚合边界 43519.3.1 eBidder:在线拍卖案例研究 43519.3.2 与不变条件保持一致 43719.3.3 与事务和一致性保持一致 43919.3.4 忽略用户界面影响 44019.3.5 避免无用的集合与容器 44119.3.6 不要专注于HAS-A关系 44119.3.7 重构聚合 44119.3.8 满足业务用例——非现实环境 44119.4 实现聚合 44219.4.1 选择一个聚合根 44219.4.2 引用其他聚合 44619.4.3 实现持久化 45019.4.4 实现事务一致性 45419.4.5 实现最终一致性 45519.4.6 实现并发性 45819.5 要点 459第20章 工厂 46120.1 工厂的作用 46120.1.1 从构造中分离出应用 46220.1.2 封装内部事物 46220.1.3 隐藏创建类型的决策 46420.1.4 聚合上的工厂方法 46620.1.5 用于重构的工厂 46720.1.6 务实地使用工厂 46920.2 要点 469第21章 存储库 47121.1 存储库 47121.2 一种被误解的模式 47321.2.1 存储库是一种反模式吗 47321.2.2 领域模型和持久化模型之间的区别 47421.2.3 通用存储库 47521.3 聚合持久化策略 47721.3.1 使用能在不损坏领域模型的情况下将其映射到数据模型的持久化框架 47821.3.2 使用不能在不影响领域模型的情况下直接映射它的持久化框架 47821.3.3 公共接收器和设置器 47921.3.4 使用备忘录模式 48021.3.5 事件流 48221.3.6 求真务实 48321.4 存储库是一个明确的约定 48321.5 事务管理和工作单元 48421.6 保存或不保存 48821.6.1 持久化追踪领域对象变更的框架 48921.6.2 必须将变更显式保存到聚合 49021.7 充当防止损坏层的存储库 49121.8 存储库的其他职责 49121.8.1 实体ID生成 49221.8.2 集合汇总 49421.8.3 并发性 49421.8.4 审计追踪 49821.9 存储库反模式 49821.9.1 反模式:不要支持即席查询 49821.9.2 反模式:延迟加载是一种设计异味 49921.9.3 反模式:不要为了报告需要而使用存储库 49921.10 存储库实现 49921.10.1 持久化框架可以在不损坏领域模型的情况下将其映射到数据模型 50021.10.2 持久化框架不能在不损坏领域模型的情况下直接映射领域模型 55021.11 要点 586第22章 事件溯源 58722.1 将状态存储为快照的限制 58822.2 通过将状态存储为事件流来获得竞争优势 58922.2.1 时态查询 58922.2.2 投影 59022.2.3 快照 59122.3 源自事件的聚合 59122.3.1 构造 59222.3.2 持久化与再融合 59622.4 构建一个事件存储 60322.4.1 设计一种存储格式 60422.4.2 创建事件流 60522.4.3 附加到事件流 60622.4.4 查询事件流 60622.4.5 添加快照支持 60722.4.6 管理并发性 60922.4.7 一个基于SQL Server的事件存储 61322.4.8 构建你自己的事件存储是一个好主意吗 61922.5 使用专门构建的Event Store 61922.5.1 安装Greg Young的Event Store 61922.5.2 使用C#客户端库 62022.5.3 运行时态查询 62422.5.4 创建投影 62722.6 使用事件溯源的CQRS 62922.6.1 使用投影创建视图缓存 63022.6.2 CQRS和事件溯源协作 63022.7 简要复述事件溯源的好处 63122.7.1 竞争性业务优势 63122.7.2 专注于表述性行为的聚合 63122.7.3 简化的持久化 63222.7.4 更好的调试 63222.8 衡量事件溯源的代价 63222.8.1 版本控制 63222.8.2 要学习的新概念和要磨练的技能 63222.8.3 需要学习和掌握的新技术 63322.8.4 大量的数据存储需求 63322.9 额外的学习资源 63322.10 要点 633第Ⅳ部分 有效应用程序的设计模式第23章 应用程序用户界面的架构设计 63723.1 设计考量 63823.1.1 占有式UI与构成式UI的对比 63823.1.2 HTML API与数据API的对比 64023.1.3 客户端与服务器端聚合/协作对比 64123.2 示例1:用于非分布式有界上下文的一个基于HTML API的、服务器端的UI 64323.3 示例2:用于分布式有界上下文的一个基于数据API的客户端UI 65023.4 要点 658第24章 CQRS:一种有界上下文的架构 65924.1 为两个上下文维护单个模型的挑战 66024.2 用于复杂有界上下文的一种更好的架构 66124.3 命令端:业务任务 66224.3.1 显式建模意图 66224.3.2 不受展现干扰所影响的模型 66324.3.3 处理业务请求 66524.4 查询端:领域报告 66524.4.1 直接映射到数据模型的报告 66624.4.2 从领域事件中构建的具体化视图 66724.5 对CQRS的误解 66824.5.1 CQRS很难 66824.5.2 CQRS是最终一致的 66824.5.3 模型需要源自事件 66924.5.4 命令应该是异步的 66924.5.5 CQRS仅适用于消息传递系统 66924.5.6 需要将CQRS用于领域事件 66924.6 可以扩展应用程序的模式 66924.6.1 扩展读取端:一个最终一致的读取模型 67024.6.2 扩展写入端:使用异步命令 67224.6.3 对一切进行扩展 67324.7 要点 674第25章 命令:用于处理业务用例的应用程序服务模式 67725.1 区分应用程序逻辑和领域逻辑 67825.1.1 应用程序逻辑 67825.1.2 来自应用程序服务角度的领域逻辑 69025.2 应用程序服务模式 69025.2.1 命令处理程序 69025.2.2 发布/订阅 69425.2.3 请求/回复模式 69625.2.4 async/await 69825.3 测试应用程序服务 69925.3.1 使用领域专业术语 69925.3.2 测试尽可能多的功能 70025.4 要点 702第26章 查询:领域报告 70326.1 有界上下文中的领域报告 70426.1.1 从领域对象中派生报告 70426.1.2 直接访问数据存储 71026.1.3 从事件流构建投影 71626.2 跨有界上下文的领域报告 72326.2.1 复合UI 72326.2.2 单独的报告上下文 72426.3 要点 726