第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,说明契约粒度已经越过可接受边界。

分布模式:Remote Facade + DTO 调用栈客户端进程业务代码(调用 Facade)Remote Facade(粗粒度接口)DTO(打包请求数据)序列化 → 网络传输网络边界服务端进程反序列化 → 解包 DTORemote Facade(接收调用)领域对象(细粒度操作)DTO(打包响应数据)Facade 减少调用次数,DTO 减少传输次数——两者配合跨越网络边界
分布模式族只有两个模式但解决核心问题:Remote Facade 提供粗粒度接口减少调用次数, DTO 把多个字段打包成一次传输。两者配合让分布式调用尽可能少地跨越网络边界。

分布边界决策图

先看是否存在部署独立性、团队所有权或故障隔离的硬约束;没有约束就保持本地。有约束时再看调用画像,集中处理 DTO、超时、幂等键和 Partial Failure,拒绝聊天式的细粒度远程调用。

分布边界:先证明约束,再承担网络成本没有硬约束保持本地少一次网络失败保留清晰端口确有分布约束Facade + DTO超时 + 幂等键可观测恢复拒绝细粒度远程逐字段 getter实体镜像无条件重试评审证据:所有权 × 部署独立性 × 故障隔离 × 调用画像先证明必须分布,再把网络责任集中在粗粒度契约
分布不是默认的演化步骤:没有真实约束就保持本地,必须分布时集中处理 DTO、超时、幂等和恢复。

选择与拒绝矩阵

评审问题选择第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《企业应用架构模式》与公开模式目录权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…