18.11 记录集
用内存中的表格数据表示一组记录,便于与数据库和数据感知界面交换。
学习目标
- 能解释记录集如何把查询结果表示成可交换的内存表格,并识别它与领域对象的边界
- 能编写 TypeScript 记录集,保留列模式、空值和类型信息,并实现安全的筛选与转换
- 能根据查询复杂度、行为归属、批量交换和演化成本,判断何时采用或拒绝记录集
为什么 18.11 记录集 值得单独学习
用内存中的表格数据表示一组记录,便于与数据库和数据感知界面交换。18.11 记录集的核心不是套用某个框架 API,而是回答:如何在数据库查询、应用服务和数据界面之间传递稳定的行列结果,同时避免把基础设施形状误当成领域对象。在订单系统基础设施替换中,如果无法说清责任、状态和失败由谁承担,即使查询能返回,架构决定也没有完成。
↡把一批同构查询行及其列信息封装成内存表格、供多个边界交换的结果表示。的价值不是让所有数据都停留在无行为的行列里,而是在简单查询、报表和批量交换中保持结构清晰。本页以 2024 年中文版公开目录限定 18.11 记录集 的范围,并依据 Martin Fowler 的作者图书页和模式目录独立重写。它不复现原书正文、插图或代码;目录只决定“要讲什么”,这里的案例、实验、判断题和答案均为本课程原创。
先建立直觉:问题、机制与代价
18.11 记录集面对的具体压力是“查询结果跨层传递、批量处理、界面绑定和对象映射成本”。它采用的机制可以概括为:以列模式和行集合承载结果,让消费者按列读取或筛选。机制带来的收益必须与弱行为表达、列名耦合、空值歧义和数据复制成本同时记录,否则学习者只会得到一个没有拒绝条件的模式名称。
对 18.11 记录集,优先比较的替代路线是:领域模型、专用 DTO、流式游标或直接的查询投影。只有在需要批量交换且行结构本身就是边界契约时,才值得引入记录集;若结果需要丰富不变量和协作行为,就应转为 ↡围绕业务行为、不变量和协作关系组织的对象模型,而不是只保存查询列的结构。,不能据此选择 18.11 记录集。
目录单元到教学证据
18.11 记录集
在 18.11 记录集 的学习边界里,18.11 记录集不是待背诵的目录词,而是用来检查“查询边界”是否把数据责任交给正确消费者。对订单系统基础设施替换,学习者要记录列模式、空值和行为归属的可观察变化,并说明它何时支持或否定 18.11 记录集。
专属设计案例:订单报表的查询结果交换
把订单报表的查询结果交换切成“数据库查询 → 记录集列模式 → 应用筛选 → 表格界面”四个观察点。18.11 记录集的设计草案必须写出谁拥有列定义、谁处理空值、失败怎样传播,以及依赖方向、对象语义、配置、测试隔离、表示转换中哪个指标最先提示当前方案不再适用。
设计记录采用五个可换行字段:单元键为 poeaa24-pattern-51-record-set;模式族为 base;裁决是“能保持列模式、空值和类型信息,批量处理记录,并说明何时对象模型更适合复杂行为”;观测项包括依赖方向、对象语义、配置、测试隔离、表示转换;拒绝条件是“记录行开始承担复杂业务不变量,或列模式无法作为边界契约维护”。
配置不是生产框架语法,而是一张评审卡。对 18.11 记录集的任何实现都要能把运行证据重新映射到这张卡;如果更换 ORM、Web 框架或部署平台后无法回答同一组问题,说明决定依赖的是工具偶然行为而不是模式语义。
结构解剖
选择与拒绝矩阵
| 评审问题 | 选择 18.11 记录集 的证据 | 应拒绝或改用其他方案的信号 |
|---|---|---|
| 责任 | 查询边界拥有列模式,消费者只读取约定字段 | 多个消费者各自猜测列名和空值含义 |
| 变化 | 报表字段可批量演化并有版本说明 | 行结构被当成公开领域对象,改列影响所有行为 |
| 失败 | 缺列、类型错误和查询失败可分别观测 | 空值或缺列被静默转换成零值 |
| 替代 | 已与 DTO、领域模型和流式结果比较内存成本 | 复杂行为持续堆在行对象的工具方法里 |
代码实践:保留列模式的订单记录集
订单报表需要把查询结果交给表格界面,但不应让界面自行猜测列名和空值。↡记录集中列名、类型、可空性和版本组成的可验证结构约束。应在查询边界定义,并在转换时拒绝不完整行。
type OrderRow = {
id: string;
totalCents: number;
status: "paid" | "open" | "cancelled";
paidAt: string | null;
};
type OrderRecordSet = {
columns: string[];
rows: OrderRow[];
};
function buildOrderRecordSet(rows: OrderRow[]): OrderRecordSet {
return {
columns: ["id", "totalCents", "status", "paidAt"],
rows,
};
}
function selectPaidRows(recordSet: OrderRecordSet): OrderRow[] {
const requiredColumns = ["id", "totalCents", "status", "paidAt"];
const hasSchema = requiredColumns.every((column) =>
recordSet.columns.includes(column),
);
if (!hasSchema) throw new Error("order record set schema mismatch");
return recordSet.rows.filter((row) => row.status === "paid");
}
const orders = buildOrderRecordSet([
{ id: "order-7", totalCents: 59700, status: "paid", paidAt: "2026-08-07" },
{ id: "order-8", totalCents: 12000, status: "open", paidAt: null },
]);
const paidOrders = selectPaidRows(orders);这段代码把列名、状态和可空时间明确成边界契约,报表可以批量筛选而不用创建完整领域对象。若订单行开始需要退款、权限或状态转移等不变量,就应把它们映射到领域模型,而不是继续往记录集中堆行为。
常见误区
可验证练习
练习
问题 1:判断边界。 一个订单行需要执行退款前检查库存和权限,它仍适合直接放在记录集中吗?
问题 2:处理列演化。 数据库把 totalCents 改名为 amountCents,报表界面应该自行兼容两个名字吗?
问题 3:选择替代方案。 一个结果集只有三列,但会被多个接口分页、缓存和导出,是否应立刻构造完整领域对象?
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 记录集
把一批同构查询行及其列信息封装成内存表格、供多个边界交换的结果表示。
- 列模式
记录集中列名、类型、可空性和版本组成的可验证结构约束。
- 领域模型
围绕业务行为、不变量和协作关系组织的对象模型,而不是只保存查询列的结构。
本章小结
掌握 18.11 记录集的标志不是记住定义,而是能在订单报表的查询结果交换中解释“数据库查询 → 记录集列模式 → 应用筛选 → 表格界面”的责任链,利用依赖方向、对象语义、配置、测试隔离、表示转换作出可证伪的选择,并在记录行开始承担复杂不变量或列模式无法维护时明确拒绝。
前后导航
来源与改写范围
- Martin Fowler 作者图书页:核对全书主题、教程与模式参考结构。
- Martin Fowler 模式目录:核对模式名称、所属模式族和作者公开摘要。
- Pearson 出版社页面:交叉核对英文版出版信息与目录范围。