第 1 章 引言 1
1.1 决策的后果与模式 2
1.1.1 单元测试 2
1.1.2 单元测试与集成测试的比例 3
1.2 设计模式及其失效分析 5
1.3 架构设计模式及其失效分析 10
1.3.1 可扩展性与弹性 11
1.3.2 开发速度 12
1.3.3 微服务的复杂性 12
小结 14
第 2 章 代码重复不一定是坏事:代码重复与灵活性的权衡 15
2.1 代码库间的通用代码及重复代码 16
2.1.1 添加新需求导致的代码重复 17
2.1.2 实现新的业务需求 17
2.1.3 结果评估 19
2.2 通过库在代码库之间共享代码 19
2.2.1 共享库的取舍与不足 20
2.2.2 创建共享库 21
2.3 抽取代码为一个独立的微服务 22
2.3.1 采用独立微服务方式的取舍与弊端 24
2.3.2 关于独立微服务的总结 27
2.4 通过代码重复改善松耦合 28
2.5 利用继承减少 API 设计中的重复 31
2.5.1 抽取出一个请求处理器作为基类 33
2.5.2 继承与紧耦合的取舍 35
2.5.3 继承与组合的取舍 36
2.5.4 一贯性的重复与偶然性的重复 37
小结 38
第 3 章 异常及其他——代码错误的处理模式 39
3.1 异常的层次结构 40
4
3.2 代码异常处理的最佳模式 44
3.2.1 公共 API 的已检测异常处理 45
3.2.2 公共 API 的未检测异常处理 46
3.3 异常处理的反模式 47
3.3.1 异常时,关闭资源 49
3.3.2 反模式:利用异常控制应用流 51
3.4 源自第三方库的异常 51
3.5 多线程环境中的异常 54
3.6 使用 Try 以函数式的途径处理异常 59
3.6.1 在生产代码中使用 Try 62
3.6.2 混合使用 Try 与抛出异常的代码 64
3.7 异常处理策略的性能对比 65
小结 68
第 4 章 灵活性与复杂性的权衡 70
4.1 一个健壮但无法扩展的API 71
4.1.1 设计一个新组件 71
4.1.2 从最简单的代码开始 72
4.2 允许客户使用自己的指标框架 75
4.3 通过钩子为你的 API提供可扩展性 77
4.3.1 防范钩子 API 的过度使用 79
4.3.2 钩子 API 的性能影响 81
4.4 通过侦听器为你的 API提供可扩展性 83
4.4.1 使用侦听器与钩子的取舍 84
4.4.2 设计的不可修改性 85
4.5 API 的灵活性分析及维护开销的权衡 87
小结 88
第 5章 过早优化 vs 热路径优化:影响代码性能的决策 89
5.1 过早优化是万恶之源 90
5.1.1 构建账户处理管道 90
5.1.2 依据错误的假设进行优化处理 91
5.1.3 对性能优化进行基准测试 92
5.2 代码中的热路径 94
5.2.1 从软件系统的角度理解帕累托法则 96
5.2.2 依据 SLA 配置线程(并发用户)数 97
5.3 具有潜在热路径的 word服务 97
5.3.1 获取每日一词 98
5.3.2 验证单词是否存在 100
5.3.3 使用 HTTP 服务,向外提供WordsService 100
5.4 检测你代码中的热路径 102
5.4.1 使用 Gatling 创建 API 的性能测试 102
5.4.2 使用 MetricRegistry 度量代码路径 105
5.5 改进热路径的性能 107
5.5.1 为现有代码创建 JMH 微基准测试 107
5.5.2 利用缓存优化 word-exists程序 109
5.5.3 调整性能测试,使用更多的输入单词 113
小结 115
第 6 章 API 的简洁性 vs 维护成本 116
6.1 一个为其他工具服务的基础库 117
6.1.1 创建云服务客户端 117
6.1.2 漫谈认证策略 119
6.1.3 理解配置的机制 120
6.2 直接暴露依赖库的配置 123
6.3 一个将依赖库的配置抽象化的工具 127
6.4 为云服务客户端库添加新的配置 129
6.4.1 为批处理工具添加新配置 130
6.4.2 为流处理工具添加新配置 132
6.4.3 方案对比:用户体验的友好性 vs 维护成本 132
6.5 弃用/删除云服务客户端库的某个配置 133
6.5.1 删除批处理工具的某个配置 135
6.5.2 删除流服务中某个配置 137
6.5.3 两种方案用户体验与维护成本的比较 138
小结 139
第 7 章 高效使用日期和时间数据 140
7.1 日期和时间信息的概念 141
7.1.1 机器时间:时间戳、纪元以及持续时间 141
7.1.2 民用时间:日历系统、日期时间以及期间 145
7.1.3 时区、UTC 以及 UTC偏移量 149
7.1.4 让人头疼的日期和时间概念 154
7.2 准备处理日期和时间信息 155
7.2.1 对范畴做限定 155
7.2.2 澄清日期和时间的需求 157
7.2.3 使用恰当的库或者包 161
7.3 实现日期和时间代码 162
7.3.1 保持概念的一致性 162
7.3.2 通过避免使用默认值提升可测试性 164
7.3.3 以文本方式表示日期和时间 170
7.3.4 通过注释解释代码 175
7.4 有必要单独指出并测试的极端情况 178
7.4.1 日历计算 178
7.4.2 发生在午夜时分的时区转换 178
7.4.3 处理不明确或者跳过的时间 179
7.4.4 处理不断变化的时区数据 179
小结 183
第 8 章 利用机器的数据本地性和内存 184
8.1 数据本地性是什么 184
8.1.1 将计算移动到数据处 185
8.1.2 用数据本地性扩展数据处理 186
8.2 数据的分区 188
8.2.1 线下大数据分区 188
8.2.2 分区和分片的区别 190
8.2.3 分区算法 191
8.3 连接多个分区上的大数据集 193
8.3.1 在同一台物理机上连接数据 194
8.3.2 需要数据移动的连接 195
8.3.3 利用广播优化连接 196
8.4 在内存还是磁盘中进行数据处理的权衡 198
8.4.1 基于磁盘的处理 198
8.4.2 我们为什么需要映射-化简? 198
8.4.3 计算访问时间 201
8.4.4 基于内存的处理 202
8.5 用 Apache Spark 实现连接 203
8.5.1 不使用广播的连接 204
8.5.2 使用广播的连接 206
小结 208
第 9 章 第三方库:你用的库成为你的代码 209
9.1 引用一个库就要对它的配置选项负责:小心那些默认配置 210
9.2 并发模型和可扩展性 213
9.2.1 使用异步和同步 API 215
9.2.2 分布式的可扩展性 217
9.3 可测试性 218
9.3.1 测试库 219
9.3.2 用伪造值和模拟函数来进行测试 221
9.3.3 集成测试工具包 224
9.4 第三方库的依赖 225
9.4.1 避免版本冲突 226
9.4.2 太多的依赖 227
9.5 选择和维护第三方依赖 228
9.5.1 第 一印象 228
9.5.2 复用代码的不同方式 229
9.5.3 锁定供应商 229
9.5.4 软件许可证 230
9.5.5 库和框架 230
9.5.6 安全和更新 230
9.5.7 选择第三方库的检查列表 231
小结 232
第 10 章 分布式系统的一致性和原子性 233
10.1 数据源的至少一次传输语义 234
10.1.1 单节点服务之间的网络访问 234
10.1.2 应用程序重试请求 235
10.1.3 生成数据和幂等性 236
10.1.4 理解 CQRS 238
10.2 去重库的简单实现 240
10.3 在分布式系统里实现去重会遇到的常见错误242
10.3.1 单节点环境 242
10.3.2 多节点环境 244
10.4 用原子性的逻辑避免竞争条件 246
小结 250
第 11 章 分布式系统的传输语义 251
11.1 事件驱动应用程序的架构 252
11.2 基于 Apache Kafka 的生产者和消费者应用程序 254
11.2.1 Kafka 消费者 255
11.2.2 理解 Kafka brokers设置 257
11.3 生产者的逻辑 258
11.4 在消费者端实现不同的传输语义 262
11.4.1 消费者手动提交 264
11.4.2 从最早或最晚的偏移量开始重启 266
11.4.3 (最终)恰好一次传输语义 268
11.5 用传输保证提供容错能力 270
小结 271
第 12 章 版本管理和兼容性 272
12.1 版本管理的抽象思考 273
12.1.1 版本的属性 273
12.1.2 向后兼容性和向前兼容性 274
12.1.3 语义版本规范 275
12.1.4 营销版本 277
12.2 库的版本管理 277
12.2.1 源码、二进制和语义兼容性 278
12.2.2 依赖图和菱形依赖 285
12.2.3 处理破坏性改动的技术手段 288
12.2.4 管理内部库 292
12.3 网络 API 的版本管理 293
12.3.1 网络 API 调用的环境 293
12.3.2 用户喜欢公开透明的版本策略 295
12.3.3 常见的版本策略 295
12.3.4 版本管理额外的考虑因素 300
12.4 数据存储的版本管理 303
12.4.1 简要介绍 Protocol Buffers 303
12.4.2 哪些是破坏性改动 305
12.4.3 在存储系统内部迁移数据 306
12.4.4 准备好面对未知 309
12.4.5 分离网络 API 和存储的数据格式 310
12.4.6 评估存储格式 312
小结 313
第 13 章 紧跟最新技术趋势和维护旧代码之间的权衡 315
13.1 什么时候应该使用依赖注入框架 316
13.1.1 DIY 依赖注入 317
13.1.2 使用依赖注入框架 319
13.2 什么时候应该使用响应式编程 321
13.2.1 创建一个单线程阻塞式处理模型 322
13.2.2 使用CompletableFuture 324
13.2.3 实现一个响应式方案 326
13.3 什么时候应该使用函数式编程 328
13.3.1 用非函数式语言写函数式代码 328
13.3.2 尾部递归优化 331
13.3.3 利用不可变性 332
13.4 对比延迟和急切初始化 333
小结 335