18.1 入口

用对象封装对外部系统或资源的访问,把协议、错误和供应商细节隔离在边界。

学习目标

  • 能解释入口如何把外部协议、错误和供应商变化隔离在稳定的内部边界之后
  • 能编写 TypeScript Gateway 契约与适配实现,把超时、重试和外部错误翻译成应用可理解的结果
  • 能根据变化轴、测试隔离和调用者数量的证据,判断何时引入或拒绝 Gateway

为什么 18.1 入口值得单独学习

订单系统通常要发邮件、调用支付 API 或写入消息队列。若每个领域服务都直接拼接 URL、处理供应商错误并解释协议状态,外部变化会沿着调用链扩散,测试也不得不启动真实基础设施。Gateway 的价值是给内部代码一个稳定的语义入口,把外部协议留在边界对象里。

不是“把所有依赖藏起来”的万能包装,而是一条有责任范围的边界:它可以翻译协议和错误,却不应替领域服务作出价格、权限或订单状态等业务决定。好的入口让变化局部可见,坏的入口只是把复杂度换了一个文件名。

本章只覆盖 2024 年中文版公开目录中的 18.1 入口,依据作者图书页和公开模式目录独立重写。案例、实验、代码、练习和答案均为本课程原创,不复现原书正文、插图或代码。

先建立直觉:稳定契约隔离易变协议

把一次通知拆成“调用者 → 内部契约 → Gateway → 外部协议 → 结果”五个观察点。描述“发送订单确认”,而不是暴露 SMTP 状态码、HTTP header 或某家 SDK 的异常类型。

Gateway 还承担。外部系统返回 429 时,调用者需要知道“可稍后重试”;返回无效收件人时,需要知道“不可重试并记录业务原因”。如果 Gateway 直接把第三方异常透传,调用者仍然被供应商绑定。

边界的另一面是。接口放在哪里、实现由谁组装、测试替身如何注入,都是设计证据。只有当外部变化真实存在、调用者足够多或测试隔离有收益时,间接层才值得付出维护成本。

目录单元到教学证据

18.1 入口

本章精确对应 manifest 单元 poeaa24-pattern-41-gateway。学习边界是:用一个专属 Gateway 封装订单系统的外部通知,并通过稳定契约、错误翻译和可替换实现证明依赖方向。

完成本单元要交出三样证据:一张调用者、Gateway 和外部系统的责任图;一段可测试的 TypeScript 契约与适配实现;一个故障样本,证明超时、限流或供应商错误不会穿透成调用者必须理解的 SDK 细节。

专属案例:订单确认通知

订单服务只应提出“发送订单确认”这一内部意图。EmailGateway 负责把它转换为 SMTP 或第三方邮件 API 的请求,统一处理超时、限流和无效地址,并返回 sentretryablerejected 三类结果。订单服务仍负责决定是否允许发送、是否记录订单事件,以及重复发送是否幂等。

评审卡记录如下:单元键为 poeaa24-pattern-41-gateway;模式族为 base;采用条件是“外部协议易变、调用者超过一个且需要测试替身”;观测项包括外部错误分类、超时率、重试次数、调用者依赖数量和替身覆盖率;拒绝条件是“没有变化轴、入口开始承载业务规则,或只增加一层转发却没有隔离证据”。

专属可视化实验:看依赖停在哪里

先预测:当 SMTP 改成 HTTP 邮件 API 时,领域代码应不应修改?点击主图观察稳定契约如何停在 Gateway 左侧、协议细节如何被限制在右侧,以及测试替身为什么能在不启动外部系统的情况下验证调用者行为。

Gateway:封装外部访问的统一入口领域代码gateway.send(msg)只依赖内部契约EmailGateway协议适配 · 错误翻译可替换为测试替身外部系统SMTP / API / MQ协议复杂、易变核心价值:• 调用者不感知外部协议细节 • 统一错误翻译 • 测试时替换为 Fake/Stub• 常见实例:Table Data Gateway、Row Data Gateway、Remote FacadeGateway 封装外部系统访问,调用者只依赖内部契约
Gateway 封装对外部系统或复杂资源的访问,调用者只依赖内部契约, 可用测试替身替换外部系统,统一翻译错误。

代码实践:翻译协议而不是泄漏协议

下面的 TypeScript 草图把外部客户端作为注入依赖。Gateway 只返回内部结果,不把供应商异常类型传给订单服务;重试策略仍应由明确的可靠性策略和幂等键约束。

type SendConfirmation = {
  orderId: string;
  email: string;
  idempotencyKey: string;
};
 
type DeliveryResult =
  | { kind: "sent"; providerId: string }
  | { kind: "retryable"; reason: "timeout" | "rate-limited" }
  | { kind: "rejected"; reason: "invalid-address" | "blocked" };
 
type MailClient = {
  send(input: { to: string; body: string; idempotencyKey: string }): Promise<{
    status: number;
    providerId?: string;
  }>;
};
 
export class EmailGateway {
  constructor(private readonly client: MailClient) {}
 
  async sendConfirmation(input: SendConfirmation): Promise<DeliveryResult> {
    try {
      const response = await this.client.send({
        to: input.email,
        body: renderConfirmation(input.orderId),
        idempotencyKey: input.idempotencyKey,
      });
      if (response.status === 202 && response.providerId) {
        return { kind: "sent", providerId: response.providerId };
      }
      if (response.status === 408 || response.status === 429) {
        return {
          kind: "retryable",
          reason: response.status === 408 ? "timeout" : "rate-limited",
        };
      }
      return { kind: "rejected", reason: "blocked" };
    } catch {
      return { kind: "retryable", reason: "timeout" };
    }
  }
}

这里有四个可验收边界:内部方法不暴露 SMTP 或 SDK 类型;外部状态被翻译为有限结果;请求带幂等键;Gateway 不决定订单是否已付款。若调用者开始捕获 VendorRateLimitError,或 Gateway 为了“方便”直接读取订单数据库,就应把它标成越界样本。

选择与拒绝矩阵

评审问题采用入口的证据应拒绝或改用其他方案的信号
变化外部协议、供应商或资源访问确实会变没有变化轴,只是为抽象而抽象
契约调用者依赖稳定语义和有限错误集合调用者仍理解 SDK 异常和状态码
测试可注入 Fake/Stub,不启动真实基础设施测试必须连接供应商才能验证业务
责任入口做协议适配,业务规则留在应用层入口开始决定订单、权限或库存
可靠性超时、限流与幂等边界有指标自动重试没有上限或重复副作用

常见误区

本章小结

  • Gateway 用稳定内部契约隔离外部协议、供应商错误和基础设施变化。
  • 它可以翻译错误和处理可靠性边界,但不应接管订单、权限或库存等业务决定。
  • Fake/Stub、有限结果类型、超时分类和幂等键是可验证的工程证据。
  • 没有变化轴或没有测试隔离收益时,应拒绝额外间接层;透传 SDK 也不算完成隔离。

可验证练习

练习

问题 1:找出泄漏。 OrderService 捕获 VendorRateLimitError 并根据 HTTP 429 决定重试。哪一层应拥有这段知识?

问题 2:设计替身。 请为 EmailGateway 写一个最小 Fake,验证订单服务传入幂等键,并在测试中模拟一次限流结果。

问题 3:判断是否引入。 一个内部函数只被一个调用者使用,协议稳定且测试不需要隔离。请给出拒绝 Gateway 的理由;若供应商下月宣布协议迁移,再列出两个新增证据。

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

入口

封装外部系统或资源访问、向内部调用者提供稳定语义契约的对象。

稳定契约

调用者依赖的输入和结果语义,独立于供应商协议与 SDK 类型。

错误翻译

把外部状态码、异常和网络失败转换成内部有限错误分类的行为。

依赖方向

代码从业务调用者指向基础设施实现的关系;边界应让业务依赖内部契约。

测试替身

在测试中代替真实外部系统、可记录输入并返回确定结果的 Fake 或 Stub。

前后导航

参考资料

资料与写作方式声明

本章以Martin Fowler《企业应用架构模式》与公开模式目录权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

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

讨论

评论区加载中…