目录
前言 1
第0 章 概述 11
第一部分 构建支持领域模型的架构
第1 章 领域建模 23
1.1 什么是领域模型 24
1.2 探索领域语言 .27
1.3 单元测试领域模型 28
1.3.1 数据类非常适合值对象 34
1.3.2 值对象和实体 36
1.4 并非一切都必须是对象:领域服务函数 38
1.4.1 基于Python 的魔法方法利用Python 使用我们的模型 39
1.4.2 异常也可以表达领域概念 40
第2 章 存储库模式 43
2.1 持久化我们的领域模型 .44
2.2 一些伪代码:我们需要什么 44
2.3 将DIP 应用于数据访问 .45
2.4 回顾:我们的模型 47
2.4.1 一般的ORM 方式:模型依赖于ORM 47
2.4.2 反转依赖关系:ORM 依赖于模型 .49
2.5 介绍存储库模式 52
2.5.1 抽象的存储库 53
2.5.2 什么是权衡 54
2.6 构建用于测试的假存储库现在变得微不足道 .58
2.7 在Python 中,什么是端口和适配器 59
2.8 总结 .60
第3 章 一个简要的插曲:关于耦合和抽象 .63
3.1 抽象状态有助于增加可测试性 64
3.2 选择正确的抽象 68
3.3 实现我们所选择的抽象 .69
3.3.1 使用伪造和依赖注入进行边缘到边缘测试 72
3.3.2 为什么不直接修补呢.74
3.4 总结 .77
第4 章 我们的第一个用例:Flask API 和服务层 79
4.1 将我们的应用程序连接到真实世界.81
4.2 第一个端到端测试 81
4.3 直接实现83
4.4 需要数据库检查的错误条件 84
4.5 引入服务层,并使用FakeRepository 对其进行单元测试 86
4.6 为什么一切都被称为服务 91
4.7 将事物放入文件夹以查看它们的归属 92
4.8 总结 .93
第5 章 高速挡和低速挡的TDD 97
5.1 我们的测试金字塔看起来怎么样 .98
5.2 应该将领域层测试转移到服务层吗.98
5.3 在决定编写哪种测试时 100
5.4 高速挡和低速挡 .101
5.5 将服务层测试与领域完全解耦 101
5.5.1 改进:在Fixture 函数中保留所有领域依存关系 .102
5.5.2 添加缺失的服务 103
5.6 将改进贯穿到E2E 测试中.104
5.7 总结 106
第6 章 工作单元模式 107
6.1 工作单元与存储库协作 109
6.2 通过集成测试驱动UoW . 110
6.3 工作单元及其上下文管理器 . 112
6.3.1 真正的工作单元使用SQLAlchemy 会话 . 113
6.3.2 用于测试的假工作单元 . 113
6.4 在服务层中使用工作单元(UoW) . 115
6.5 提交/ 回滚行为的显式测试 . 116
6.6 显式提交与隐式提交 117
6.7 示例:使用工作单元将多个操作分组到一个原子单元中 118
6.7.1 示例1:重新分配 118
6.7.2 示例2:更改批次数量 119
6.8 整理集成测试 119
6.9 总结 120
第7 章 聚合和一致性边界 123
7.1 为什么不直接使用电子表格运行所有内容 124
7.2 不变量、约束和一致性 124
7.3 什么是聚合 126
7.4 选择聚合.127
7.5 一个聚合= 一个存储库 130
7.6 关于性能.132
7.7 乐观并发控制与版本号 133
7.8 测试我们的数据完整性规则 .137
7.8.1 使用数据库事务隔离级别强制执行并发规则 138
7.8.2 悲观并发控制示例:SELECT FOR UPDATE 139
7.9 总结 140
7.10 第一部分概述 141
第二部分 事件驱动架构
第8 章 事件和消息总线 145
8.1 避免造成混乱 146
8.1.1 首先,让我们避免弄乱我们的Web 控制器 146
8.1.2 不要弄乱我们的模型147
8.1.3 或者尝试一下使用服务层 148
8.2 单一职责原则 149
8.3 全部都使用消息总线 149
8.3.1 模型记录事件 .149
8.3.2 事件是简单的数据类150
8.3.3 模型触发事件 .150
8.3.4 消息总线将事件映射到处理程序 .152
8.4 选项1:服务层从模型中获取事件并将其添加到消息总线 .153
8.5 选项2:服务层自己触发事件 .154
8.6 选项3:工作单元将事件发布到消息总线 155
8.7 总结 159
第9 章 深入探讨消息总线 163
9.1 新的需求引导我们走向新的架构 164
9.2 将服务层方法重构为消息处理程序167
9.2.1 消息总线现在从工作单元(UoW)收集事件 169
9.2.2 我们所有的测试也是根据事件编写的 171
9.2.3 丑陋的处理方法:消息总线必须返回结果 .172
9.2.4 修改API 以处理事件 .172
9.3 实现我们的新需求 .173
9.4 测试驱动新的处理程序 174
9.4.1 实现 .175
9.4.2 领域模型的新方法 177
9.5 选读:使用模拟消息总线对事件处理程序进行单元测试 .178
9.6 总结 181
9.6.1 我们实现了什么 181
9.6.2 我们为什么这么实现181
第10 章 命令与命令处理程序 183
10.1 命令与事件 .183
10.2 异常处理的差异 185
10.3 讨论:事件,命令,以及错误处理 187
10.4 同步错误恢复 190
10.5 总结 192
第11 章 事件驱动架构:使用事件集成微服务 195
11.1 分布式泥球和名词思维 196
11.2 分布式系统中的错误处理 199
11.3 替代方案:使用异步消息进行时间解耦 .200
11.4 使用Redis 发布/ 订阅通道进行集成 201
11.5 使用端到端测试进行全面测试驱动 202
11.5.1 Redis 是另一个围绕消息总线的轻量适配器 204
11.5.2 新发出的事件 205
11.6 内部事件与外部事件 .206
11.7 总结 206
第12 章 命令?C 查询责任分离 209
12.1 领域模型用于写 210
12.2 大部分用户不会买你的家具 211
12.3 Post/Redirect/Get 和CQS 213
12.4 抓紧你的午餐,朋友们 215
12.5 测试CQRS 视图 216
12.6 “显而易见”的替代方案1:使用现有存储库 217
12.7 领域模型未针对读取操作进行优化 218
12.8 “显而易见”的替代方案2:使用ORM 219
12.9 SELECT N 1 和其他性能考量 .219
12.10 是时候完全随波逐流了 .220
12.11 更改读取模型的实现很容易 224
12.12 总结 .225
第13 章 依赖注入和Bootstrapping 227
13.1 隐式依赖与显式依赖 .229
13.2 显式依赖不是很奇怪和Java 式吗 230
13.3 准备处理程序:带有闭包和部分函数的手动DI .233
13.4 使用类的备选方案 234
13.5 Bootstrap 脚本 235
13.6 消息总线在运行时提供处理程序 238
13.7 在程序入口使用Bootstrap 240
13.8 在测试中初始化DI 241
13.9 “正确”构建适配器:一个可用示例 243
13.9.1 定义抽象和具体实现 243
13.9.2 为你的测试完成一个假的版本 244
13.9.3 找出如何集成测试真实事物的方法 .245
13.10 总结 .247
后记 . 249
附录A 图表和表格汇总 267
附录B 项目结构模板 269
附录C 交换基础设施:全部使用CSV 279
附录D 存储库和工作单元Django 模式 . 285
附录E 验证 295