第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 如何补上查询和集合边界。
选择时记录字段变更次数、映射重复率、类型错误发现时机和调试成本;小项目不要仅因配置“看起来灵活”就接受运行时失败。
元数据查询流水线
一次安全的元数据查询应经过领域意图、条件组合、映射校验、参数化执行和类型恢复五步;任一步失败都应返回可定位结果,而不是把未经验证的行对象交给领域层。
选择与拒绝矩阵
| 评审问题 | 选择元数据映射方案的证据 | 应拒绝当前方案的信号 |
|---|---|---|
| 复用 | 映射规则重复且可由统一校验覆盖 | 只有一两个类,却引入难调试的通用引擎 |
| 查询 | Query Object 能组合、参数化并测试 | 查询条件散落为字符串拼接 |
| 集合 | Repository 只暴露领域集合语义 | 领域层直接依赖表名、列名和 SQL |
| 失败 | 元数据错误在启动或适配器边界被发现 | 错误配置直到生产查询才变成类型异常 |
常见误区
可验证练习
练习
本组练习覆盖第13章对象-关系元数据映射模式,并要求把元数据校验、查询组合、资源库边界、参数化和类型恢复映射到订单聚合持久化。
问题 1:决定驱动方式。 只有两个实体、字段几乎不变但团队非常重视编译期类型检查,是否应该马上使用元数据驱动?
问题 2:组合查询。 订单筛选条件来自用户输入,Query Object 和 Repository 分别应该承担什么?
问题 3:定位配置错误。 元数据把 order_status 错写成 order_state,应该在哪里失败?
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 元数据映射
用配置、注解或结构化描述表达对象字段与关系列对应关系,再由通用映射器解释读写动作的方式。
- 查询对象
把筛选、排序和分页条件封装为可组合值对象,再交给数据源适配器解释的查询表达。
- 资源库
把一组实体的查找、添加和移除操作包装为领域集合接口并隐藏数据源细节的边界。
- 元数据校验
在启动或执行查询前检查字段、列名、类型和参数规则是否符合映射契约的步骤。
本章小结
掌握第13章对象-关系元数据映射模式的标志不是记住配置文件格式,而是能在订单聚合持久化中解释“领域意图 → 查询对象 → 元数据校验 → 参数化执行 → 类型结果”的责任链。学习者应利用元数据校验、查询组合、资源库边界、参数化和类型恢复作出可证伪的选择,并在配置无校验、查询拼接用户输入或资源库泄漏 SQL 时明确拒绝当前实现。
前后导航
来源与改写范围
- Martin Fowler 作者图书页:核对全书主题、教程与模式参考结构。
- Martin Fowler 模式目录:核对模式名称、所属模式族和作者公开摘要。
- Pearson 出版社页面:交叉核对英文版出版信息与目录范围。