第15章 分布模式
用粗粒度外观和数据载体控制网络往返、失败传播与内部模型泄漏。
学习目标
- 能解释 Remote Facade、Data Transfer Object 与远程调用边界各自承担的变化和成本
- 能用 TypeScript 为订单结算调用设计粗粒度契约、DTO 校验、超时与幂等处理
- 能根据所有权、部署独立性、往返画像和部分失败证据决定分布或保持本地
为什么第15章分布模式值得单独学习
第15章分布模式的核心不是把本地接口搬到网络上,而是承认延迟、序列化、超时和部分失败会改变调用语义。订单服务调用结算服务时,如果客户端逐字段读取远程对象,三次成功的请求也可能在第四次超时;如果接口直接暴露内部实体,字段改名又会变成跨进程发布事故。
↡把多个远程操作组合成少数业务意图调用的边界,使客户端不必为一次用例往返多次。减少网络往返,但它必须保留清晰的业务意图,不能成为所有规则的无边界入口。本页依据 2024 年中文版公开目录限定第15章范围,并根据 Martin Fowler 的作者图书页和模式目录独立重写;目录只决定要讲什么,案例、实验、判断题和答案均为本课程原创。
↡在进程边界传输的稳定数据结构,只包含用例需要的字段,不暴露服务端内部实体和行为。把多个字段打包成一次传输,并为版本兼容提供显式边界。
先建立直觉:问题、机制与代价
↡把远程调用当作本地方法调用并逐项读取结果的接口风格,容易累积往返、放大延迟并泄漏远端对象。看起来熟悉,却不能隐藏网络边界。调用次数、载荷大小、序列化时间和超时比例必须进入评审记录。
↡客户端为一次业务意图生成的稳定标识,服务端用它去重,避免重试把同一写操作执行多次。让重试从“可能重复扣款”变成“可判断的同一请求”。
↡一次跨进程调用中部分步骤成功、部分步骤失败的状态,必须由协议明确恢复、补偿或人工介入路径。要求协议说明已经发生的副作用、可安全重试的范围和最终一致性的观察方式。
对第15章分布模式,优先比较的替代路线是:没有部署独立性、所有权或故障隔离约束时保持本地;确实跨进程时用 Remote Facade 减少往返,用 Data Transfer Object 隔离模型,用超时、幂等键和可观测状态处理 Partial Failure。若实验只能证明一次成功响应,不能说明重试和兼容版本,就不能据此选择分布模式。
目录单元到教学证据
第15章分布模式
第15章分布模式要求把“远程调用是否值得承担网络成本”变成可观察的架构决定:先冻结版次、应用切片与目录坐标,再从问题与语境比较候选模式,手算远程调用和映射成本,验证正常样本与边界样本,注入一个故障并定位首差,最后让独立复核者重放结果。本文把这些证据映射到订单结算切片的契约、日志和测试中。
专属代码案例:订单与结算跨进程调用
把订单结算切成“客户端意图 → Remote Facade → DTO → 网络边界 → 结算用例”五个观察点。设计草案必须写出谁拥有订单状态、谁作出扣款决定、失败怎样传播,以及契约粒度、往返、序列化、版本和 Partial Failure 中哪个指标最先提示方案不再适用。
设计记录采用五个可换行字段:单元键为 poeaa24-chapter-15-distribution-patterns;模式族为 distribution;裁决是“用一次粗粒度业务调用传输稳定 DTO,并把超时、幂等和失败状态显式化”;观测项包括契约粒度、往返、序列化、版本、部分失败;拒绝条件是“把本地细粒度实体接口原样暴露到网络”。
先预测:结算请求超时后,客户端能否直接重试?哪些字段应由 DTO 暴露,哪些状态必须由结算服务持有?先写下边界、幂等键和恢复路径,再阅读实现;验证时要能说明成功、重复提交、版本不兼容和服务端已扣款但响应丢失四种状态。
type SettleOrderRequest = {
orderId: string;
amountCents: number;
currency: "CNY" | "USD";
idempotencyKey: string;
};
type SettleOrderResponse =
| { status: "settled"; receiptId: string }
| { status: "already-settled"; receiptId: string }
| { status: "rejected"; reason: "amount-mismatch" | "currency-unsupported" };
interface SettlementApplication {
settle(request: SettleOrderRequest): SettleOrderResponse;
}
class OrderRemoteFacade {
constructor(private readonly application: SettlementApplication) {}
settleOrder(input: SettleOrderRequest): SettleOrderResponse {
if (input.amountCents <= 0 || input.idempotencyKey.length < 8) {
return { status: "rejected", reason: "amount-mismatch" };
}
return this.application.settle({ ...input });
}
}这段代码让 Remote Facade 负责稳定的业务意图入口和边界校验,让结算应用拥有扣款规则,让 DTO 只携带用例需要的数据。生产实现还需要在传输层设置超时、记录请求标识并返回可重放的状态;不能把内部订单实体、数据库连接或任意方法表面化为远程 API。
分布模式调用栈
订单客户端通过 Remote Facade 发起一次粗粒度调用,DTO 在网络边界前后完成序列化与反序列化,服务端再调用内部结算用例。图中的每一层都应有可测试的所有者;如果客户端需要连续调用多个 getter,说明契约粒度已经越过可接受边界。
分布边界决策图
先看是否存在部署独立性、团队所有权或故障隔离的硬约束;没有约束就保持本地。有约束时再看调用画像,集中处理 DTO、超时、幂等键和 Partial Failure,拒绝聊天式的细粒度远程调用。
选择与拒绝矩阵
| 评审问题 | 选择第15章分布模式的证据 | 应拒绝或改用其他方案的信号 |
|---|---|---|
| 约束 | 所有权、部署独立性或故障隔离确实要求跨进程 | 只是因为框架或团队习惯而分布 |
| 粒度 | 一次调用表达完整业务意图,往返次数有基线 | 客户端逐字段 getter 或逐步拼装远程实体 |
| 数据 | DTO 只暴露稳定用例字段,版本规则可测试 | 远程接口直接返回内部实体和行为 |
| 失败 | 超时、重试、幂等和补偿状态可观测 | 只能看到最终错误,无法知道副作用是否已发生 |
常见误区
可验证练习
练习
本组练习覆盖第15章分布模式,并要求把远程粒度、DTO、版本和部分失败映射到订单结算切片。
问题 1:决定是否分布。 订单与结算由同一团队维护、共享数据库且没有独立扩缩容需求,但客户端希望“更现代”的服务边界,应该如何判断?
问题 2:设计一次粗粒度调用。 结算页面需要订单金额、币种、优惠和当前支付状态,怎样避免四次远程往返?
问题 3:处理响应丢失。 服务端可能已扣款但响应在网络中丢失,客户端如何恢复而不重复扣款?
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- Remote Facade
把多个远程操作组合成少数业务意图调用的边界,用于减少网络往返。
- Data Transfer Object
在进程边界传输的稳定数据结构,只包含用例需要的字段。
- Remote Procedure Call
把远程调用呈现为本地方法调用的接口风格,必须显式承认网络成本。
- Idempotency Key
让服务端识别同一业务请求并安全去重的稳定标识。
- Partial Failure
部分步骤成功、部分步骤失败且需要协议定义恢复路径的状态。
本章小结
掌握第15章分布模式的标志不是记住两个英文名词,而是能在订单结算切片中解释“客户端意图 → Remote Facade → DTO → 网络边界 → 结算用例”的责任链。学习者应利用契约粒度、往返、序列化、版本、超时和 Partial Failure 作出可证伪的选择,并在没有硬约束、细粒度 getter、实体镜像和无条件重试时明确拒绝当前实现。
前后导航
来源与改写范围
- Martin Fowler 作者图书页:核对全书主题、教程与模式参考结构。
- Martin Fowler 模式目录:核对 Remote Facade、Data Transfer Object 与分布模式族的公开摘要。
- Pearson 出版社页面:交叉核对英文版出版信息与目录范围。