08-仓储与映射-DDD领域驱动设计


title: "08 仓储(Repository)与映射"


学习目标

  • 理解仓储(Repository)的本质:用"集合式接口"表达聚合的持久化,隐藏数据访问细节
  • 能解释"仓储接口在领域层、实现放基础设施层"的原因(依赖规则 + 可测试性)
  • 能识别"查询/读模型"与"聚合仓储"应当分离的场景(避免仓储膨胀)

核心概念

仓储是什么

仓储为聚合提供类似集合(Collection)的操作语义:

  • 保存聚合(save)
  • 通过标识加载聚合(findById)
  • 删除聚合(delete)

仓储不是 DAO 的同义词:DAO 往往面向表/行;仓储面向聚合。

本书示例:领域层 OrderRepository + 基础设施层 OrderRepositoryImpl。

映射(Mapping)是什么

在 DDD 分层里,经常需要把领域对象与持久化对象(PO/Entity)互相转换:

  • Domain Model(Order/VO) ↔ Persistence Object(OrderPO/OrderItemPO)

映射逻辑通常属于基础设施层(或专门的 mapper 层),避免领域层被 ORM/JSON 等技术细节污染。

示例:OrderRepositoryImpl 内部实现了 toOrderPO(...)、toOrder(...) 等转换。

课堂讲授:为什么仓储接口应该属于领域层

如果你把仓储接口放在基础设施层,那么领域层就会出现:

  • 对基础设施层的依赖(违反依赖规则)
  • 难以替换实现(内存/数据库/远程服务)
  • 难以测试(领域/应用层测试必须引入基础设施)

把接口放在领域层意味着:领域模型定义"我需要什么能力";基础设施层负责"我如何实现"。

伪代码示例:仓储的"集合式接口"

示例:OrderRepository。

text 复制代码
interface OrderRepository {
  save(order: Order)
  findById(orderNumber: OrderNumber): Optional<Order>
  findByCustomerId(customerId: CustomerId): List<Order>
  delete(orderNumber: OrderNumber)
}

课堂提醒:findByCustomerId 这类"列表查询"在真实项目里容易让仓储膨胀。是否放在仓储,要看它是否仍然符合"按聚合加载"的目的;更复杂的查询通常会走 CQRS/读模型(本书 16 章选读)。

伪代码示例:映射(Domain ↔ PO)

示例:仓储实现把领域对象映射为 PO,再用 restore(...) 之类的工厂方法恢复聚合。

text 复制代码
class OrderRepositoryImpl implements OrderRepository {
  save(order):
    po = toOrderPO(order)
    store.put(order.orderNumber.value, po)

  findById(orderNumber):
    po = store.get(orderNumber.value)
    if po is null: return empty
    return Optional.of(toOrder(po))

  toOrder(po):
    items = loadItemPOs(po.orderId).map(toOrderItem)
    return Order.restore(
      OrderNumber.of(po.orderId),
      CustomerId.of(po.customerId),
      Status.valueOf(po.status),
      items,
      po.createTime
    )
}

常见误区

  • 误区 1:仓储返回/接收 PO 而不是聚合:领域层会被持久化模型"绑架"
  • 误区 2:仓储方法无限膨胀:什么查询都往里塞,最终变成"万能查询服务"
  • 误区 3:把跨聚合的事务写进仓储:仓储应该关注一个聚合的持久化,不负责业务编排
  • 误区 4:把 ORM 注解塞进领域模型:导致领域模型依赖框架,测试与迁移困难

课堂练习

  1. 请解释:为什么要用 restore(...) 来恢复聚合,而不是直接 new Order(...)?
  2. 假设新增一个查询需求:"按订单状态分页查询订单",你会放在 OrderRepository 吗?给出你的理由,并提供一个替代方案(例如读模型/查询服务接口)。
  3. 画出一张"依赖方向图":Domain(接口)← Infrastructure(实现),并指出如果反过来会发生什么。

自测题

  1. 仓储与 DAO 的区别是什么?
  2. 为什么说"仓储面向聚合"?
  3. 映射逻辑应该放在哪里?为什么不应该放在领域层?

延伸阅读

相关推荐
谢亮_vipxieliang4 分钟前
JavaScript this 指向完全指南:调用位置决定一切
开发语言·javascript·ecmascript
IT研究室6 分钟前
最新计算机毕业设计选题推荐-基于spring boot的文学名著分享系统-网站-文档指导-Java-springboot
java·spring boot·课程设计
denlaku9 分钟前
JDK 13 新特性详解
java·开发语言
秋饼10 分钟前
Java 后端企业级 LLM 网关实战:Spring Cloud Gateway 构建统一大模型接入层
java·ai·技术分享·后端开发
码云之上10 分钟前
Prompt 拦不住 Chatbot 的安全问题:九大风险与分层防护
人工智能·架构·全栈
对讲机数码科普10 分钟前
从电路原理到协议栈:模拟对讲机与数字对讲机的技术内核拆解
运维·网络·架构·软件工程
一个有温度的技术博主12 分钟前
单点登录(SSO)到底怎么实现?怎么判断用户是否已单点登录?
架构
今年下半年18 分钟前
【防重放】Spring Boot 通过自定义注解实现接口防重放
java·spring boot·自定义注解·防重放
念越21 分钟前
题库、练习与错题本代码阅读
开发语言·数据结构
WeiXin_DZbishe22 分钟前
基于Node.js的马拉松赛事管理系统的设计与实现-计算机毕设 附源码【课程设计】90211
java·javascript·vue.js·python·php·课程设计