9.3 表模块

用一个实例处理某张表或视图全部行的业务逻辑,在记录集与面向对象行为之间取得集合式平衡。

学习目标

  • 能用一个表模块实例集中处理订单表的一组行,并说清它拥有的业务责任
  • 能比较表模块、事务脚本和领域模型在规则复杂度、协作与演化成本上的取舍
  • 能用正常、临界和受控故障场景验证事务边界,并识别何时应撤回表模块

为什么 9.3 表模块值得单独学习

表模块不是把每一行机械地包装成对象,而是用一个实例处理一组记录的业务逻辑,在记录集与面向对象行为之间取得集合式平衡。它适合规则围绕表或视图集合变化、但还没有足够理由为每一行建立完整领域对象的场景。

订单折扣与授信能暴露这个取舍。订单表模块可以集中处理折扣计算、授信校验和批量更新,让多个入口复用同一套集合规则;但如果每个订单都有复杂生命周期、不变量和跨对象协作,表模块的集合边界就可能遮住真正的领域边界。

边界必须能回答谁拥有筛选条件、谁保护集合不变量、谁开启事务,以及失败时哪些行应一起回退。

Row Set 是表模块每次操作的实际输入,必须带着筛选条件、版本和预期变更一起进入验证,不能只传一串没有语义的行号。

先建立直觉:集合责任要有明确所有者

事务脚本适合少量步骤和单一用例;当多个入口都要处理订单表的相同集合规则时,复制脚本会让折扣、额度和状态判断逐渐分叉。

领域模型适合复杂、频繁变化且需要对象协作的规则,但会引入对象边界和持久化映射成本。表模块位于两者之间:它集中一组记录的行为,却不承诺每一行都拥有完整对象身份。

先预测:如果订单查询、批量折扣和授信校验都读取同一张订单表,应该让控制器各写一遍条件,还是让 OrderTableModule 统一维护集合规则?判断依据不是类名,而是规则是否围绕同一记录集变化,以及事务能否把相关行作为一个一致性单元处理。

目录单元到教学证据

9.3 表模块

9.3 表模块 的单元边界内,学习者要把订单折扣与授信拆成查询、计算、校验和提交四类证据:哪些操作共享同一个集合边界,哪些规则只适用于单行,哪些变化需要对象协作。单元键为 poeaa24-pattern-03-table-module;核心证据是相同规则从两个入口调用时仍只有一个实现,并且失败能定位到事务中的首个差异。

评审记录还要回答:表模块实例拥有哪张表或视图,集合筛选条件是否稳定,批量操作的事务范围是什么,数据库约束与代码规则如何分工,什么时候应迁移到领域模型。如果只能说“ORM 有一个模块类”,却说不出这些边界,模式选择还没有完成。

专属代码案例:订单折扣与授信

下面的代码把订单表模块的责任固定为集合操作。它不模拟 ORM API,而是刻意让评审看到:同一实例负责加载订单、应用折扣、校验额度和提交结果;调用者不能绕过模块直接改写中间状态。

type OrderRow = {
  id: string;
  customerId: string;
  totalCents: number;
  discountCents: number;
  status: "pending" | "approved" | "rejected";
};
 
type CreditSnapshot = { availableCents: number };
 
class OrderTableModule {
  constructor(private readonly rows: OrderRow[]) {}
 
  approveBatch(ids: string[], credit: CreditSnapshot): OrderRow[] {
    const selected = this.rows.filter((row) => ids.includes(row.id));
    const required = selected.reduce(
      (sum, row) => sum + row.totalCents - row.discountCents,
      0,
    );
    if (required > credit.availableCents) {
      throw new Error("credit limit exceeded");
    }
    for (const row of selected) row.status = "approved";
    return selected;
  }
}

这段代码的最小证据合同是:选择条件由表模块拥有,批量总额计算集中在模块内,授信失败不会部分批准,成功状态只在校验通过后改变。生产实现还需把数据库事务、并发保护和消息投递接入同一张决策记录,不能把内存示例当成完整一致性保证。

模式结构图

9.3 表模块:集合规则集中,边界责任清楚用例入口查询 / 批量折扣授信 / 重试Table ModuleTable Boundary筛选 → 计算 → 校验批量状态更新外部边界事务 / 数据库消息 / 补偿规则围绕记录集合变化;单行生命周期复杂时迁移到领域模型集合责任集中,不等于模块拥有系统所有责任
表模块集中一组记录的业务规则,同时把事务协调和更复杂的对象协作留在明确边界之外。

模式结构图先显示调用方、表模块实例、记录集合与授信边界的责任流;它帮助评审区分“集合规则集中”与“所有业务都塞进一个大类”。

表模块的事务边界

9.3 表模块:验证与回退边界读取快照Row Set版本与筛选计算规则折扣 / 总额保留中间证据授信校验正常 / 临界超额 → 首差提交或回退全部批准冲突 → 不写回只改变一个约束:保存首差、版本、回退与补偿证据成功与半失败都必须能重放
表模块的选择证据必须覆盖批量成功、临界额度、超额拒绝和并发冲突等边界。

一次批量批准应在读取快照、计算总额、校验额度和写回状态之间保持明确边界。若数据库更新与外部通知不在同一事务中,必须额外记录消息状态和补偿路径,不能把提交成功误认为所有副作用都已完成。

选择与拒绝矩阵

评审问题选择表模块的证据应拒绝或迁移的信号
责任一个实例拥有同一表或视图的集合规则多个入口各自复制折扣和授信条件
边界相关行的筛选、计算和提交范围稳定每个订单都需要独立生命周期与跨对象不变量
变化集合规则变化集中且可由批量测试覆盖小改动同时触及对象协作、外部通知和多个聚合
失败事务能定位首个差异并整体回退只能看到最终错误,无法说明哪些行已改变
替代已与事务脚本和领域模型比较并保留撤回路径仅因 ORM 习惯或类名相似而选择

常见误区

可验证练习

练习

本组练习围绕 9.3 表模块,要求把集合责任、事务边界和迁移信号写入同一张评审卡。

问题 1:判断边界。 两个入口都要按客户和订单状态筛选待授信订单,应该如何证明共用表模块比复制事务脚本更合适?

问题 2:处理临界样本。 订单总额恰好等于可用额度时应批准,超过一分钱时应全部拒绝,评审要保存什么证据?

问题 3:决定迁移。 表模块开始同时处理订单生命周期、客户风险策略、外部通知和跨聚合不变量,应该继续扩展吗?

名词解释

名词解释

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

Table Module

以一张表或视图的记录集合为边界,集中提供这组记录相关业务操作的对象组织方式。

Table Boundary

决定哪些记录可以一起读取、计算、校验和提交的集合责任边界。

Transaction Script

由一个用例过程直接组织步骤、读取数据并调用规则的实现方式。

Domain Model

用协作对象表达业务状态、行为和不变量的组织方式,适合复杂且频繁变化的规则。

Row Set

在一次表模块操作中由筛选条件确定、需要共同计算或提交的一组记录。

本章小结

掌握 9.3 表模块的标志,不是记住“一个类处理一张表”,而是能在订单折扣与授信中解释“选择记录 → 计算集合规则 → 校验额度 → 一致提交”的责任链。表模块应集中稳定的集合行为,事务脚本、领域模型和应用服务分别承担不同的变化与协调责任;当单行生命周期、跨聚合不变量或外部副作用成为主要变化轴时,应明确迁移和撤回条件。

前后导航

来源与改写范围

资料与写作方式声明

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

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

讨论

评论区加载中…