13.3 资源库

在领域与数据映射层之间提供类似集合的接口,集中表达领域对象的获取和保存意图。

学习目标

  • 能解释资源库如何用领域语言隔离聚合获取与持久化细节,并确定其责任边界
  • 能编写 TypeScript 资源库接口和内存实现,覆盖查找、保存与按条件查询的契约
  • 能根据聚合粒度、查询复杂度、事务归属和测试成本,判断何时采用或拒绝资源库

为什么 13.3 资源库 值得单独学习

在领域与数据映射层之间提供类似集合的接口,集中表达领域对象的获取和保存意图。13.3 资源库的核心不是套用某个框架 API,而是回答:如何让领域服务用业务语言表达获取聚合,同时不暴露 SQL、ORM 和映射生命周期。在订单聚合持久化中,如果无法说清责任、状态和失败由谁承担,即使正常请求能够返回,架构决定也没有完成。

的价值不是把每条查询都包一层,而是让持久化细节服从聚合边界和领域语言。本页以 2024 年中文版公开目录限定 13.3 资源库 的范围,并依据 Martin Fowler 的作者图书页和模式目录独立重写。它不复现原书正文、插图或代码;目录只决定“要讲什么”,这里的案例、实验、判断题和答案均为本课程原创。

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

13.3 资源库面对的具体压力是“聚合身份、查询组合、事务协作、映射替换和测试隔离”。它采用的机制可以概括为:领域层依赖集合式接口,具体实现协调 Mapper、缓存和工作单元。机制带来的收益必须与新增抽象、查询泄漏和接口膨胀成本同时记录,否则学习者只会得到一个没有拒绝条件的模式名称。

对 13.3 资源库,优先比较的替代路线是:直接查询服务、专用查询对象、数据访问对象或活动记录。只有在需要围绕聚合根统一获取、保存和事务语义时,才值得引入资源库;若资源库只是把每个 SQL 原样转发,或者接口暴露了数据库行,就应拒绝当前设计。决定资源库的粒度,而不是表的数量。

目录单元到教学证据

13.3 资源库

13.3 资源库 的学习边界里,13.3 资源库不是待背诵的目录词,而是用来检查“领域意图”是否把持久化责任交给正确对象。对订单聚合持久化,学习者要记录查询语言、事务归属和映射细节的可观察变化,并说明它何时支持或否定 13.3 资源库。

专属设计案例:订单聚合持久化

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

设计记录采用五个可换行字段:单元键为 poeaa24-pattern-24-repository;模式族为 mapping;裁决是“能让领域服务使用领域词汇查询聚合,证明资源库没有承载业务规则或返回持久化细节”;观测项包括元数据校验、查询组合、资源库边界、参数化、类型恢复;拒绝条件是“资源库接口暴露表结构,或无法说明聚合事务边界”。

配置不是生产框架语法,而是一张评审卡。对 13.3 资源库的任何实现都要能把运行证据重新映射到这张卡;如果更换 ORM、Web 框架或部署平台后无法回答同一组问题,说明决定依赖的是工具偶然行为而不是模式语义。

结构解剖

Repository:领域层看到集合,不见 DB领域服务repo.findById(42)repo.save(order)repo.findBy(spec)OrderRepositoryfindById(id): Ordersave(order): voidfindBy(spec): Order[]实现DataMapperSQL / ORMIdentityMapUnitOfWorkRepository 的职责边界:• 对领域层暴露集合式接口(add/remove/find),隐藏持久化细节• 内部协调 DataMapper + IdentityMap + UnitOfWork 完成实际 I/O• 一个聚合根对应一个 Repository,粒度 = 聚合边界领域层通过 Repository 像操作集合一样存取聚合,不见数据库
Repository 在领域层和数据源之间建立集合式接口。 领域代码像操作内存集合一样存取聚合,底层持久化细节完全隐藏。

选择与拒绝矩阵

评审问题选择 13.3 资源库 的证据应拒绝或改用其他方案的信号
责任资源库围绕聚合根提供获取、保存和查询意图资源库承担折扣、审批等业务规则
变化ORM、SQL 和缓存可以在实现侧替换接口泄漏表名、列名和分页框架细节
失败映射错误、并发冲突和事务失败可分类观测资源库吞掉异常并返回空集合
替代已与专用查询对象、DAO 和活动记录比较只是为了“看起来像领域层”而包裹单条 SQL

代码实践:用集合式接口隔离订单持久化

领域服务只依赖 OrderRepository,而内存实现可以在测试中替换真实数据库。应保持小而稳定;复杂报表可走专用查询对象,不必硬塞进聚合资源库。

type Order = {
  id: string;
  status: "open" | "paid" | "cancelled";
  totalCents: number;
};
 
interface OrderRepository {
  findById(id: string): Order | undefined;
  findByStatus(status: Order["status"]): Order[];
  save(order: Order): void;
}
 
class InMemoryOrderRepository implements OrderRepository {
  constructor(private readonly orders: Order[] = []) {}
 
  findById(id: string): Order | undefined {
    return this.orders.find((order) => order.id === id);
  }
 
  findByStatus(status: Order["status"]): Order[] {
    return this.orders.filter((order) => order.status === status);
  }
 
  save(order: Order): void {
    const index = this.orders.findIndex(
      (candidate) => candidate.id === order.id,
    );
    if (index === -1) this.orders.push(order);
    else this.orders[index] = order;
  }
}
 
function markPaid(repository: OrderRepository, id: string): void {
  const order = repository.findById(id);
  if (!order) throw new Error("order not found");
  repository.save({ ...order, status: "paid" });
}

这段代码让业务用 findByIdsave 表达聚合意图,测试可以注入内存实现。生产实现可以协调 Mapper 与事务,但不应把 SQL、ORM 查询对象或数据库行类型放进 OrderRepository 接口。

常见误区

可验证练习

练习

问题 1:划分职责。 OrderRepository 中有一个 approvePayment 方法,它应该保留吗?

问题 2:设计失败。 findByStatus 在数据库超时时返回空数组,为什么危险?

问题 3:选择替代方案。 运营报表需要跨十张表聚合,并不返回订单聚合。应强行加入 OrderRepository 吗?

名词解释

名词解释

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

资源库

在领域与数据映射层之间提供集合式接口、集中表达聚合获取与保存意图的对象。

聚合根

资源库所管理的、拥有一致性边界和业务身份的对象入口。

领域查询意图

以业务词汇描述筛选、排序或分页意图、避免把 SQL 或表结构泄漏到领域层的查询约束。

本章小结

掌握 13.3 资源库的标志不是记住定义,而是能在订单聚合持久化中解释“领域意图 → 资源库接口 → 映射实现 → 数据源 → 聚合结果”的责任链,利用元数据校验、查询组合、资源库边界、参数化、类型恢复作出可证伪的选择,并在接口泄漏表结构或资源库承载业务规则时明确拒绝。

前后导航

来源与改写范围

资料与写作方式声明

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

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

讨论

评论区加载中…