第18章 基本模式

用网关、映射器、注册表和接口边界隔离基础设施变化与对象语义。

学习目标

  • 能区分 Gateway、Mapper、Registry、Layer Supertype 与 Separated Interface 的依赖责任和适用边界
  • 能用 TypeScript 把订单领域对象与外部存储表示隔离,并为替换基础设施保留可测试端口
  • 能根据变化轴、对象语义、配置和测试隔离成本决定是否引入基本模式

为什么第18章基本模式值得单独学习

基本模式不是一组可以无条件堆叠的工具类,而是隔离变化轴的小型构件。订单系统要从旧仓储迁移到新 API 时,真正重要的问题不是“再加一个 Service 类”,而是调用者是否依赖了外部表示、对象创建方式和全局查找细节。

把外部变化留在边界内。本页依据 2024 年中文版公开目录限定第18章范围,并根据 Martin Fowler 的作者图书页和模式目录独立重写;目录只决定要讲什么,案例、实验、判断题和答案均为本课程原创。

避免把数据库行、远程 JSON 和领域对象混成一个不稳定的类型。

先建立直觉:小边界,大责任

适合确实需要运行时发现或共享生命周期的场景,不应成为默认的全局依赖入口。

只有当层内规则稳定且确实需要共享时才值得引入。

让领域层定义需要的端口,而不是让基础设施决定领域层的抽象。

对第18章基本模式,优先比较的替代路线是:存在外部变化时使用 Gateway,存在对象表示变化时使用 Mapper,需要运行时发现时谨慎使用 Registry,层内规则稳定时才采用 Layer Supertype,跨包依赖需要由使用者控制时采用 Separated Interface。若没有明确变化轴却先创建全局入口、抽象工厂和多层映射,就不能据此选择基本模式。

目录单元到教学证据

第18章基本模式

第18章基本模式要求把构件选择变成可复核证据:冻结版次、应用切片与目录坐标;从问题和语境比较候选模式;手算转换和间接层成本;验证正常样本与边界样本;只注入一个替换故障并定位首差;让独立复核者重放结果并接入发布门禁。本文把这些证据映射到订单存储替换的端口、适配器、映射测试和依赖检查中。

专属代码案例:订单系统基础设施替换

把订单查询切成“用例 → Separated Interface → Gateway → 外部记录 → Mapper → 领域对象”六个观察点。设计草案必须写出谁拥有领域规则、谁负责表示转换、失败怎样传播,以及依赖方向、对象语义、配置、测试隔离和表示转换中哪个指标最先提示方案不再适用。

设计记录采用五个可换行字段:单元键为 poeaa24-chapter-18-base-patterns;模式族为 base;裁决是“让领域端口拥有语义,让 Gateway 和 Mapper 吸收基础设施变化,并让替换失败可在边界被定位”;观测项包括依赖方向、对象语义、配置、测试隔离、表示转换;拒绝条件是“全局 Registry 或 Mapper 让领域对象重新依赖外部记录结构”。

先预测:旧 API 返回 snake_case 记录而新 API 返回嵌套 JSON,订单用例应该修改哪里?如果测试替身要替换真实网关,接口应该由谁定义?先写下端口、映射和失败边界,再阅读实现;验证时要能说明替换外部系统不会改变领域对象的金额和状态语义。

type OrderRecord = {
  order_id: string;
  total_cents: number;
  state: "open" | "paid";
};
 
type Order = {
  id: string;
  totalCents: number;
  state: "open" | "paid";
};
 
interface OrderGateway {
  find(orderId: string): Promise<OrderRecord | null>;
}
 
class OrderMapper {
  toDomain(record: OrderRecord): Order {
    return {
      id: record.order_id,
      totalCents: record.total_cents,
      state: record.state,
    };
  }
}
 
class FindOrder {
  constructor(
    private readonly gateway: OrderGateway,
    private readonly mapper: OrderMapper,
  ) {}
 
  async execute(orderId: string): Promise<Order | null> {
    const record = await this.gateway.find(orderId);
    return record ? this.mapper.toDomain(record) : null;
  }
}

这里由 Separated Interface 让用例依赖 OrderGateway,由 Gateway 封装外部访问,由 Mapper 把记录转换为领域对象。替换旧 API 时只需要更换 Gateway 实现和必要的映射测试;不要让领域层直接读取 order_id 或把外部 JSON 作为领域实体传递。

基本模式依赖关系

Gateway 封装访问,Mapper 翻译表示,Registry 提供可控的查找点,Layer Supertype 共享层内稳定规则,Separated Interface 控制接口的归属。图中的箭头不是“模式越多越好”,而是用来检查依赖方向是否仍然由调用者的语义驱动。

基础模式:依赖关系图Registry全局查找点(Service Locator)Gateway封装外部系统访问Mapper对象间翻译(不改变双方)Layer Supertype层内公共基类(如 BaseEntity)Separated Interface接口与实现分包实现接口继承基类这 5 个模式是其他所有 POEAA 模式的构建块Gateway 封装访问、Mapper 翻译、Registry 查找、其余两个管理依赖方向
基础模式族是其他所有 POEAA 模式的构建块。Gateway 封装外部系统访问, Mapper 在对象间翻译,Registry 提供全局查找,Layer Supertype 和 Separated Interface 管理依赖方向。

基本模式边界决策图

先问“哪条变化轴正在发生”,再问“边界是否能减少该变化的传播”。没有外部变化就不要加 Gateway,没有表示差异就不要加 Mapper,没有运行时发现需求就不要加 Registry;选择 Separated Interface 时要确保接口归使用者拥有。

基本模式:先找变化轴,再增加边界外部访问变化Gateway封装连接与失败保留领域语义表示与依赖变化Mapper + Separated Interface控制转换方向使用者拥有语义没有变化轴拒绝全局 Registry拒绝万能基类保持直接依赖评审证据:变化轴 × 依赖方向 × 测试替换成本边界的价值是吸收变化,不是增加层数
基本模式应由变化轴驱动:先找到会变化的边界,再选择 Gateway、Mapper 或接口分离。

选择与拒绝矩阵

评审问题选择第18章基本模式的证据应拒绝或改用其他方案的信号
变化轴外部访问、表示转换或模块依赖确实变化没有变化轴却预先增加多层间接结构
责任用例拥有语义,Gateway 和 Mapper 拥有基础设施细节领域对象直接读取外部字段或创建连接
查找Registry 的生命周期和替换策略有测试全局 Registry 隐藏所有依赖,测试只能依赖环境
继承Layer Supertype 的规则稳定且只服务同一层基类同时承载跨层状态和业务规则

常见误区

可验证练习

练习

本组练习覆盖第18章基本模式,并要求把 Gateway、Mapper、接口归属和测试隔离映射到订单替换切片。

问题 1:确定边界。 旧订单 API 返回平面记录,新 API 返回嵌套 JSON,但领域用例的订单语义不变,应该修改哪里?

问题 2:选择 Registry。 测试中需要为每个用例传入不同的网关替身,但生产环境希望按租户动态选择实现,是否应该让所有用例直接访问全局 Registry?

问题 3:评估基类。 订单层和客户层都有 createdAt,是否应让所有领域对象继承一个包含数据库保存、审计和 HTTP 请求的 Layer Supertype?

名词解释

名词解释

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

Gateway

封装外部系统或资源访问的边界,让上层依赖业务语义而不是基础设施细节。

Mapper

在两种对象表示之间转换并明确字段、身份和缺失值规则的组件。

Registry

按名称或键提供共享对象查找的集中入口,需要明确全局状态与生命周期代价。

Layer Supertype

为同一层的多个类提供稳定公共行为或约束的基类。

Separated Interface

把接口放在使用者边界一侧,把实现放在另一模块以控制依赖方向的做法。

本章小结

掌握第18章基本模式的标志不是记住五个构件名称,而是能在订单系统基础设施替换中解释“用例 → Separated Interface → Gateway → 外部记录 → Mapper → 领域对象”的责任链。学习者应利用依赖方向、对象语义、配置、测试隔离和表示转换作出可证伪的选择,并在没有变化轴的间接层、万能 Mapper、全局 Registry 和跨层基类时明确拒绝当前实现。

前后导航

来源与改写范围

资料与写作方式声明

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

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

讨论

评论区加载中…