第13章 对象-关系元数据映射模式

以元数据、查询对象和资源库提升映射复用与领域表达。

学习目标

  • 能解释 Metadata Mapping、Query Object 与 Repository 如何分别隔离映射、查询和集合访问
  • 能用 TypeScript 编写带元数据校验、参数化条件和类型恢复的数据源适配边界
  • 能根据类型安全、映射复用、查询组合和领域隔离的证据选择代码驱动或元数据驱动方案

为什么第13章对象-关系元数据映射模式值得单独学习

第13章对象-关系元数据映射模式的核心不是把表名换成配置文件,而是决定“对象如何被翻译”“查询意图如何组合”“集合如何被领域使用”。订单聚合的字段、筛选条件和集合操作往往在多个用例中重复;如果每个调用者都手写 SQL 和行对象转换,映射错误会分散在业务代码中,也无法统一验证。

应把可变化的结构描述与领域行为分开,但也要承担运行时校验、调试和类型恢复成本。本文以 2024 年中文版公开目录限定第13章的范围,并依据 Martin Fowler 的作者图书页和模式目录独立重写;目录只决定要讲什么,案例、实验、判断题和答案均为本课程原创。

先建立直觉:问题、机制与代价

让领域意图脱离 SQL 字符串。

则负责集合边界,不应偷偷变成跨聚合的万能查询器。

把配置错误尽早变成可定位的失败,而不是让错误行对象流入领域。

对第13章对象-关系元数据映射模式,优先比较的替代路线是:小而稳定的模型使用代码驱动映射获得类型安全,映射规则重复且结构变化频繁时使用元数据映射;查询条件用 Query Object 组合,聚合集合用 Repository 暴露。若实验只能显示查询执行成功,不能说明元数据错误、参数化和类型恢复如何处理,就不能据此选择方案。

目录单元到教学证据

第13章 对象-关系元数据映射模式

第13章对象-关系元数据映射模式要求把“数据结构和领域意图如何连接”变成可观察的架构决定:先标出元数据所有者、查询组合者、资源库边界和类型恢复点,再用订单聚合的字段变更、筛选组合和错误输入测试验证决定。只有映射规则能被校验、查询能被组合、领域层不依赖列名,元数据边界才有意义。

专属代码案例:订单查询与资源库

把订单聚合持久化切成“领域意图 → 查询对象 → 元数据校验 → 参数化执行 → 类型结果”五个观察点。第13章对象-关系元数据映射模式的设计草案必须写出谁拥有状态、谁作出业务决定、失败怎样传播,以及元数据校验、查询组合、资源库边界、参数化和类型恢复中哪个指标最先提示当前方案不再适用。

设计记录采用五个可换行字段:单元键为 poeaa24-chapter-13-object-relational-metadata;模式族为 mapping;裁决是“元数据只描述结构,Query Object 表达意图,Repository 隔离集合,领域层消费类型结果”;观测项包括元数据校验、查询组合、资源库边界、参数化、类型恢复;拒绝条件是“配置可以绕过校验,或 Repository 直接暴露 SQL 与表结构”。

先预测:如果把 status 改成数据库中的 order_status,哪些代码应该变化,哪些领域调用不应变化?先写下元数据、查询对象和资源库的责任,再阅读实现;验证时要能说明列名映射错误在哪里被发现,以及用户输入如何保持参数化。

type Order = {
  id: string;
  status: "open" | "paid";
  totalCents: number;
};
 
type OrderCriteria = {
  status?: Order["status"];
  minimumTotalCents?: number;
};
 
type OrderRow = {
  order_id: string;
  order_status: string;
  total_cents: number;
};
 
const orderMetadata = {
  id: "order_id",
  status: "order_status",
  totalCents: "total_cents",
} as const;
 
function toOrder(row: OrderRow): Order {
  if (row.order_status !== "open" && row.order_status !== "paid") {
    throw new Error("invalid order status");
  }
  return {
    id: row.order_id,
    status: row.order_status,
    totalCents: row.total_cents,
  };
}
 
interface OrderRepository {
  find(criteria: OrderCriteria): Order[];
}
 
class SqlOrderRepository implements OrderRepository {
  find(criteria: OrderCriteria): Order[] {
    const clauses: string[] = [];
    const parameters: unknown[] = [];
    if (criteria.status) {
      clauses.push(`${orderMetadata.status} = ?`);
      parameters.push(criteria.status);
    }
    if (criteria.minimumTotalCents !== undefined) {
      clauses.push(`${orderMetadata.totalCents} >= ?`);
      parameters.push(criteria.minimumTotalCents);
    }
    return executeParameterizedQuery(clauses, parameters).map(toOrder);
  }
}

这段代码让元数据集中描述列名,Query Object 只描述筛选意图,Repository 负责参数化执行并恢复为 Order。真实系统还要在启动时校验元数据完整性、限制可排序字段并记录查询计划;配置驱动不等于无需类型检查,也不等于允许用户输入拼接到 SQL。

元数据驱动与代码驱动对比

代码驱动映射通常类型安全、IDE 友好,但字段变化会产生重复修改;元数据驱动可以复用通用引擎,却需要启动校验、运行时诊断和类型恢复。下面的对比图同时展示 Query Object 与 Repository 如何补上查询和集合边界。

元数据映射:代码驱动 vs 元数据驱动代码驱动(手写 Mapper)每个实体类 → 一个 Mapper 类映射逻辑硬编码在代码中改表结构 → 改代码 → 重新编译优点:类型安全、IDE 友好缺点:重复代码多、维护成本高class OrderMapper { ... }元数据驱动(配置映射)映射规则存在 XML / 注解 / 配置通用引擎读取元数据 → 生成 SQL改表结构 → 改配置 → 无需编译优点:一处修改、全局生效缺点:调试难、运行时才发现错误@Column(name="order_id")VS配套模式Query Object把查询条件封装为对象可组合、可复用、可序列化Repository把集合语义包装为领域接口领域层只看到 add/get/remove元数据映射的权衡:灵活性 vs 类型安全——小项目手写,大项目配置
元数据映射模式族解决"映射规则放在哪里"。代码驱动类型安全但重复多, 元数据驱动灵活但调试难。Query Object 封装查询条件,Repository 封装集合访问。

选择时记录字段变更次数、映射重复率、类型错误发现时机和调试成本;小项目不要仅因配置“看起来灵活”就接受运行时失败。

元数据查询流水线

一次安全的元数据查询应经过领域意图、条件组合、映射校验、参数化执行和类型恢复五步;任一步失败都应返回可定位结果,而不是把未经验证的行对象交给领域层。

元数据查询:意图到类型结果的安全流水线领域意图条件 + 分页不含 SQL元数据校验字段 + 类型配置错误即失败Repository参数化 SQL查询边界类型恢复Row → Domain领域结果拒绝条件:配置未校验、用户输入拼接 SQL、领域层依赖列名每一步都要能定位失败,并保持领域意图不依赖数据库语法
元数据映射的价值在于集中结构变化,同时保留校验、参数化和类型恢复的明确边界。

选择与拒绝矩阵

评审问题选择元数据映射方案的证据应拒绝当前方案的信号
复用映射规则重复且可由统一校验覆盖只有一两个类,却引入难调试的通用引擎
查询Query Object 能组合、参数化并测试查询条件散落为字符串拼接
集合Repository 只暴露领域集合语义领域层直接依赖表名、列名和 SQL
失败元数据错误在启动或适配器边界被发现错误配置直到生产查询才变成类型异常

常见误区

可验证练习

练习

本组练习覆盖第13章对象-关系元数据映射模式,并要求把元数据校验、查询组合、资源库边界、参数化和类型恢复映射到订单聚合持久化。

问题 1:决定驱动方式。 只有两个实体、字段几乎不变但团队非常重视编译期类型检查,是否应该马上使用元数据驱动?

问题 2:组合查询。 订单筛选条件来自用户输入,Query Object 和 Repository 分别应该承担什么?

问题 3:定位配置错误。 元数据把 order_status 错写成 order_state,应该在哪里失败?

名词解释

名词解释

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

元数据映射

用配置、注解或结构化描述表达对象字段与关系列对应关系,再由通用映射器解释读写动作的方式。

查询对象

把筛选、排序和分页条件封装为可组合值对象,再交给数据源适配器解释的查询表达。

资源库

把一组实体的查找、添加和移除操作包装为领域集合接口并隐藏数据源细节的边界。

元数据校验

在启动或执行查询前检查字段、列名、类型和参数规则是否符合映射契约的步骤。

本章小结

掌握第13章对象-关系元数据映射模式的标志不是记住配置文件格式,而是能在订单聚合持久化中解释“领域意图 → 查询对象 → 元数据校验 → 参数化执行 → 类型结果”的责任链。学习者应利用元数据校验、查询组合、资源库边界、参数化和类型恢复作出可证伪的选择,并在配置无校验、查询拼接用户输入或资源库泄漏 SQL 时明确拒绝当前实现。

前后导航

来源与改写范围

资料与写作方式声明

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

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

讨论

评论区加载中…