Repository vs DAO = 业务抽象 vs 技术抽象
解决什么问题:仓储模式经常被误用为"复杂版 DAO"—— 实际上 Repository 是业务概念,DAO 是技术概念,二者粒度和目的完全不同。
业务问题
真实场景
场景 1:DAO 的"万能表"反模式
java
// 反模式:DAO 把业务对象当表
public class OrderDAO {
public void save(Order o) { ... } // 1 个 DAO = 1 个表
public Order findById(long id) { ... }
}
// 问题:
// - 接口是 SQL 风格(save/findById/)
// - 返回的 Order 是 ORM 对象(不是领域模型)
// - 业务逻辑不知道"订单聚合"的边界
// - 跨聚合修改 = 多次 save(破坏事务)Vernon Ch 12 引自 Evans 原话:
"For each type of object that needs global access, create an object that can provide the illusion of an in-memory collection of all objects of that type... Provide repositories only for aggregates."
失败模式
| 失败模式 | 后果 |
|---|---|
| DAO 万能化 | 业务逻辑分散到 DAO;事务跨多个 save |
| Repository 返回实体而非聚合 | 客户端访问内部实体 = 违反封装 |
| Repository 带业务逻辑 | 仓储退化为服务 |
| 每个实体一个 Repository | 聚合边界被忽视 |
控制论视角
Ch 6 §6.3:"解耦" = 把不同的关注点分离。
Repository vs DAO 的本质 = 业务关注点 vs 技术关注点的解耦:
领域层: Repository(业务接口)
↑
↓ 依赖反转
基础设施层:DAO(技术实现)对位:
- Repository = BC 的对外接口(业务语言)
- DAO = BC 的内部实现(技术语言)
- 依赖反转 = Ch 6 §6.3 的"解耦"在分层架构的应用
核心概念
Repository vs DAO 5 维对比
| 维度 | Repository | DAO |
|---|---|---|
| 设计意图 | 业务概念 | 技术概念 |
| 粒度 | 一个聚合类型 | 一个表/对象 |
| 接口语言 | Ubiquitous Language | 技术语言 |
| 位置 | 领域层 | 基础设施层 |
| 目的 | 业务抽象 | 数据访问 |
Repository 的本质
仓储 = 聚合的内存集合抽象
- 看上去像内存集合
- 实际可能是数据库/缓存/文件
- 客户端不知道存储细节Vernon 强调:
"Provide repositories only for aggregates." —— 只有聚合才有仓储,不是所有实体。
Repository 的方法集
基本方法(最小集):
- add(aggregate) # 新增
- remove(aggregate) # 删除
- findById(id) # 按 ID 查
- save(aggregate) # 保存(新增或更新)
扩展方法(按需):
- findAll(specification) # 按条件查
- findByXxx(...) # 业务相关查询
- count(specification) # 计数
- existsById(id) # 存在性Repository 的实现分层
python
# 领域层:接口(业务语言)
class OrderRepository(ABC):
@abstractmethod
def find_by_id(self, order_id: OrderId) -> Order: ...
@abstractmethod
def save(self, order: Order) -> None: ...
# 基础设施层:实现(技术语言)
class SqlOrderRepository(OrderRepository):
def __init__(self, session):
self.session = session
def find_by_id(self, order_id):
# SQL 细节(业务不感知)
return self.session.query(Order).filter_by(id=order_id).first()
def save(self, order):
self.session.add(order)
self.session.commit()工程实践
决策 1:选 Repository 还是 DAO
决策树:
按聚合粒度?
- 是 → Repository
- 否(按表) → DAO
业务关心"内存集合"语义?
- 是 → Repository
- 否 → DAO
接口用业务语言?
- 是 → Repository
- 否 → DAO决策 2:Repository 数量
原则:
- 仓储数 ≈ 聚合类型数(1:1)
- 不要每个实体一个仓储
- 不要"通用仓储"(违反业务语言)反模式 vs 正模式
| 反模式 | 正模式 |
|---|---|
| 每个实体一个 Repository | 每个聚合类型一个 Repository |
| Repository 返回实体 | Repository 返回聚合根 |
| Repository 带业务规则 | Repository 只做存取 |
| Repository = 万能服务 | Repository 单一职责(持久化) |
测试友好
python
# 用内存实现做单元测试
class InMemoryOrderRepository(OrderRepository):
def __init__(self):
self.orders = {}
def find_by_id(self, order_id):
return self.orders.get(order_id)
def save(self, order):
self.orders[order.id] = order
def test_order_confirm():
repo = InMemoryOrderRepository()
order = Order.create(tenant_id, customer_id)
order.confirm()
repo.save(order)
loaded = repo.find_by_id(order.id)
assert loaded.status == "CONFIRMED"性能指标
| 指标 | 衡量 | 健康值 |
|---|---|---|
| 仓储数量 | 仓储数 / 聚合类型数 | ≈ 1:1 |
| 仓储职责纯度 | 仓储方法平均复杂度 | < 5 行 |
| 查询性能 | findById 时间 | < 10ms |
| N+1 问题数 | 跨聚合查询 N+1 | 0 |
边界判断
何时用 Repository(★ 业务核心)
- 有聚合根
- 需要持久化
- 多个聚合类型
- 业务需要"内存集合"语义
何时用 DAO(★ 技术工具)
- 简单的 CRUD(无业务语义)
- 单一聚合类型(直接用 ORM)
- 读多写少的报表(专门的查询服务)
何时拆 Repository
- 聚合类型差异大(不同存储机制)
- 性能要求不同
- 业务复杂度差异大
关键洞察
- Repository = 业务抽象——DAO = 技术抽象——分层即解耦。
- 只对聚合开仓储——不是每个实体,避免聚合边界被破坏。
- 接口在领域层,实现在基础设施层——依赖反转让业务不依赖技术。
- 仓储不管理事务——事务在应用服务层,一个事务 = 多个仓储操作。
- 与 cybernetics 同构:Repository = 控制律的输入端口,DAO = 控制律的物理实现。