本书是一本面向 Rust 后端开发人员的入门参考书,通过实际项目引导读者从 0 到 1 构建一个功能齐全的电子邮件通信API。本书涵盖了广泛的主题,包括 Rust 生态系统的利用、应用结构的设计、测试的编写、用户认证和授权、错误处理策略的实施、应用状态的观察,以及持续集成和部署管道的建立等。本书不仅介绍了具体的工具和库,还深入探讨了系统设计、可观测性和易操作性等重要概念,能够帮助读者掌握专业的开发方法。 本书适合初学者,是开启 Rust 开发之旅的理想起点,即使没有 Rust 或后端开发经验,相信你也能够轻松跟上、快速入门。
Luca Palmieri是Rust London用户组的联合组织者、开源贡献者和公众演讲者,拥有多年在生产环境中运行数十个Rust服务的经验,也是几个Rust编程的积极推广者和Rust研讨会作者。
温祖彤,电子信息专业硕士在读。曾于字节跳动进行后端开发实习,于清华大学开源操作系统训练营担任Rust基础/算法讲师,在Rust以及相关领域有一定的经验和见解;曾参与多项编程/算法相关竞赛,是一名资深的开源技术以及编程爱好者,还制作过多项人工智能/算法竞赛领域相关的开源课程。李力,毕业于西安理工大学,Java技术专家,从业9年,曾在网易、Chainup、新东方在线等知名企业从事研发工作,现就职于西安腾讯云,从事于大模型知识引擎LKE开发,实践经验丰富。对分布式系统、架构设计、存储系统充满兴趣。《Scala编程实战》(第2版)译者。杨楚天,毕业于华东师范大学,任职于诺基亚,从事通信系统开发工作,有多年的 C++ 技术开发经验。在业余时间修炼 Rust,对云原生架构与开发很感兴趣。
第1章 准备工作 1
1.1 安装Rust工具链 1
1.1.1 编译目标 1
1.1.2 发布渠道 2
1.1.3 我们需要什么样的工具链 2
1.2 项目初始化 3
1.3 集成开发环境 3
1.3.1 rust-analyzer 4
1.3.2 IntelliJ Rust 4
1.3.3 应该如何选择IDE 4
1.4 内部开发循环 5
1.4.1 更快的链接 5
1.4.2 cargo-watch 6
1.5 持续集成 7
1.5.1 持续集成的步骤 8
1.5.2 准备就绪的持续集成流水线 10
第2章 构建邮件简报 12
2.1 引导示例 12
2.1.1 基于问题的学习 12
2.1.2 帮助完善本书 13
2.2 邮件简报服务应该做什么 13
2.2.1 捕捉需求:用户故事 13
2.3 循序渐进,不断迭代 14
2.3.1 准备开始 15
第3章 注册新的订阅者 16
3.1 前期准备工作 16
3.2 选择一个Web框架 17
3.3 实现第一个端点:健康检查 17
3.3.1 使用actix-web编写代码 18
3.3.2 actix-web应用程序剖析 19
3.3.3 实现健康检查处理器 24
3.4 第一次集成测试 27
3.4.1 如何对端点进行测试 27
3.4.2 应该将测试放在哪里 28
3.4.3 改变项目结构以便于测试 30
3.5 实现第一个集成测试 33
3.5.1 优化 36
3.6 重新聚焦 40
3.7 处理HTML表单 40
3.7.1 提炼需求 40
3.7.2 以测试的形式捕捉需求 41
3.7.3 从POST请求中解析表单数据 44
3.8 存储数据:数据库 52
3.8.1 选择数据库 52
3.8.2 选择数据库包 53
3.8.3 带有副作用的集成测试 55
3.8.4 数据库初始化 56
3.8.5 编写第一个查询 62
3.9 持久化一个新的订阅者 70
3.9.1 actix-web中的应用程序状态 70
3.9.2 actix-web工作进程 72
3.9.3 Data提取器 74
3.9.4 INSERT语句 74
3.10 更新测试 78
3.10.1 测试隔离 81
3.11 总结 85
第4章 遥测 86
4.1 未知的未知 86
4.2 可观测性 87
4.3 日志 88
4.3.1 log包 89
4.3.2 actix-web的Logger中间件 89
4.3.3 外观模式 90
4.4 插桩POST /subscriptions 92
4.4.1 与外部系统的交互 93
4.4.2 像用户一样思考 94
4.4.3 日志应当易于关联 96
4.5 结构化日志 98
4.5.1 tracing包 99
4.5.2 从log迁移到tracing 99
4.5.3 tracing中的跨度 100
4.5.4 插桩future 102
4.5.5 tracing的Subscriber 104
4.5.6 tracing-subscriber 105
4.5.7 tracing-bunyan-formatter 105
4.5.8 tracing-log 107
4.5.9 删除未使用的依赖 109
4.5.10 清理初始化流程 109
4.5.11 集成测试中的日志 112
4.5.12 清理插桩代码——tracing::instrument 116
4.5.13 保护隐私——secrecy 119
4.5.14 请求ID 121
4.5.15 借力tracing生态系统 124
4.6 总结 124
第5章 上线 125
5.1 我们必须讨论部署问题 125
5.2 选择工具 126
5.2.1 虚拟化:Docker 126
5.2.2 托管:DigitalOcean 127
5.3 应用程序的Dockerfile 127
5.3.1 Dockerfile 127
5.3.2 构建上下文 128
5.3.3 sqlx离线模式 129
5.3.4 运行镜像 131
5.3.5 网络 132
5.3.6 层次化配置 133
5.3.7 数据库连接 138
5.3.8 优化Docker镜像 139
5.4 部署到DigitalOcean应用平台 144
5.4.1 安装 144
5.4.2 应用规范 144
5.4.3 如何使用环境变量注入加密信息 147
5.4.4 连接到DigitalOcean的Postgres实例 149
5.4.5 应用配置中的环境变量 152
5.4.6 最后一步,推送 153
第6章 拒绝无效的订阅者(第一部分) 155
6.1 需求 156
6.1.1 姓名约束 156
6.1.2 安全约束 156
6.2 第一次实现 158
6.3 漏洞百出的验证 159
6.4 类型驱动开发 160
6.5 所有权遇见不变量 164
6.5.1 AsRef 167
6.6 panic 169
6.7 Result——将错误作为值 171
6.7.1 使解析函数返回Result类型 171
6.8 精确的断言错误:claim 174
6.9 单元测试 175
6.10 处理 Result 177
6.10.1 match 177
6.10.2 “?”操作符 178
6.10.3 400的请求错误 179
6.11 电子邮件地址格式 179
6.12 SubscriberEmail类型 180
6.12.1 拆分domain子模块 180
6.12.2 新类型的框架 181
6.13 属性测试 184
6.13.1 使用fake生成随机测试数据 184
6.13.2 quickcheck与proptest 185
6.13.3 quickcheck入门 185
6.13.4 实现Arbitrary特质 186
6.14 请求体验证 188
6.14.1 使用TryFrom重构 192
6.15 总结 195
第7章 拒绝无效的订阅者(第二部分) 196
7.1 确认邮件 196
7.1.1 订阅者的同意 196
7.1.2 确认用户的流程 197
7.1.3 实现策略 198
7.2 邮件发送组件——EmailClient 198
7.2.1 如何发送电子邮件 198
7.2.2 如何使用reqwest编写REST客户端 201
7.2.3 如何测试REST客户端 208
7.2.4 EmailClient::send_email的初版实现 213
7.2.5 加强正常的测试 221
7.2.6 处理失败情况 228
7.3 可维护测试套件的骨架和原则 237
7.3.1 为什么要编写测试 238
7.3.2 为什么不编写测试 238
7.3.3 测试代码也是代码 238
7.3.4 测试套件 239
7.3.5 测试发现 240
7.3.6 每个测试文件都是一个包 241
7.3.7 共享测试辅助函数 241
7.3.8 共享启动逻辑 244
7.3.9 构建API客户端 252
7.3.10 小结 256
7.4 重新聚焦 256
7.5 零停机部署 257
7.5.1 可靠性 257
7.5.2 部署策略 258
7.6 数据库迁移 261
7.6.1 状态存在于应用程序之外 261
7.6.2 部署与迁移 261
7.6.3 多步迁移 262
7.6.4 新的必填字段 262
7.6.5 新表 263
7.7 发送确认邮件 264
7.7.1 固定的电子邮件地址 264
7.7.2 固定的确认链接 269
7.7.3 等待确认 272
7.7.4 GET /subscriptions/confirm的骨架 276
7.7.5 整合 278
7.7.6 订阅令牌 287
7.8 数据库事务 294
7.8.1 全部成功,或者全部失败 294
7.8.2 Postgres中的事务 295
7.8.3 sqlx中的事务 295
7.9 总结 299
第8章 错误处理 300
8.1 错误处理的目的 300
8.1.1 系统内部错误 301
8.1.2 系统交互错误 303
8.1.3 小结 305
8.2 为操作人员提供错误报告 305
8.2.1 跟踪错误的根本原因 308
8.2.2 Error特质 313
8.3 控制流与错误处理 316
8.3.1 控制流的分层 316
8.3.2 使用枚举对错误建模 317
8.3.3 只有错误类型还不够 319
8.3.4 使用thiserror减少样板代码 323
8.4 防止“大泥球”型的错误枚举 324
8.4.1 使用anyhow擦除错误类型 329
8.4.2 使用anyhow还是thiserror 331
8.5 错误日志由谁来记 331
8.6 总结 333
第9章 投递邮件简报 334
9.1 用户故事在变化 334
9.2 不要向未确认的订阅者发送 335
9.2.1 使用公共API设置状态 336
9.2.2 scoped mock 337
9.2.3 绿色测试 338
9.3 所有已确认的订阅者都会收到新内容 339
9.3.1 组合测试辅助函数 339
9.4 实现策略 341
9.5 请求体的内容 341
9.5.1 测试无效输入 342
9.6 获取已确认的订阅者列表 344
9.7 发送邮件简报 346
9.7.1 context与with_context 347
9.8 验证存储的数据 348
9.8.1 责任界限 352
9.8.2 关注编译器 353
9.8.3 移除样板代码 355
9.9 简单方法的局限性 356
9.10 总结 357
第10章 API的安全性 358
10.1 认证 358
10.1.1 缺点 359
10.1.2 多因素身份验证 359
10.2 基于密码的身份验证 359
10.2.1 基本身份验证 360
10.2.2 密码验证——简单的方法 365
10.2.3 密码存储 368
10.2.4 不要阻塞异步执行器 384
10.2.5 用户枚举 390
10.3 这安全吗 395
10.3.1 传输层安全性 395
10.3.2 密码重置 395
10.3.3 交互类型 395
10.3.4 机器对机器 396
10.3.5 使用浏览器的用户 396
10.3.6 机器对机器(机器代表人) 397
10.4 插曲:下一步计划 397
10.5 登录表单 398
10.5.1 提供HTML页面 398
10.6 登录 401
10.6.1 HTML表单 401
10.6.2 登录成功后的重定向 404
10.6.3 处理表单数据 405
10.6.4 上下文错误 412
10.7 会话 443
10.7.1 基于会话的身份验证 443
10.7.2 会话存储 444
10.7.3 选择会话存储 444
10.7.4 actix-session 445
10.7.5 管理仪表板 448
10.8 种子用户 459
10.8.1 数据库迁移 459
10.8.2 密码重置 460
10.9 重构 477
10.9.1 如何实现一个actix-web中间件 478
10.10 总结 484
第11章 容错的工作流 486
11.1 重温POST /admin/newsletters 486
11.2 我们的目标 488
11.3 失败模式 488
11.3.1 无效输入 488
11.3.2 网络I/O 489
11.3.3 应用程序崩溃 490
11.3.4 作者的操作 490
11.4 幂等简介 490
11.4.1 幂等实践:支付 491
11.4.2 幂等键 492
11.4.3 并发请求 493
11.5 测试需求(第一部分) 493
11.6 实现策略 495
11.6.1 有状态的幂等:保存和重放 495
11.6.2 无状态的幂等:确定性的键生成 495
11.6.3 时间问题有些棘手 495
11.6.4 做出选择 496
11.7 幂等存储 496
11.7.1 应该使用哪种数据库 496
11.7.2 数据库模式 497
11.8 保存和重放 498
11.8.1 读取幂等键 498
11.8.2 查询已保存的响应 502
11.8.3 保存响应 505
11.9 并发请求 512
11.9.1 测试需求(第二部分) 512
11.9.2 同步 513
11.10 处理错误 520
11.10.1 分布式事务 522
11.10.2 后向恢复 522
11.10.3 前向恢复 523
11.10.4 异步处理 523
11.11 后记 539