Skip to content

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 维对比 ​

维度RepositoryDAO
设计意图业务概念技术概念
粒度一个聚合类型一个表/对象
接口语言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+10

边界判断 ​

何时用 Repository(★ 业务核心) ​

  • 有聚合根
  • 需要持久化
  • 多个聚合类型
  • 业务需要"内存集合"语义

何时用 DAO(★ 技术工具) ​

  • 简单的 CRUD(无业务语义)
  • 单一聚合类型(直接用 ORM)
  • 读多写少的报表(专门的查询服务)

何时拆 Repository ​

  • 聚合类型差异大(不同存储机制)
  • 性能要求不同
  • 业务复杂度差异大

关键洞察 ​

  1. Repository = 业务抽象——DAO = 技术抽象——分层即解耦。
  2. 只对聚合开仓储——不是每个实体,避免聚合边界被破坏。
  3. 接口在领域层,实现在基础设施层——依赖反转让业务不依赖技术。
  4. 仓储不管理事务——事务在应用服务层,一个事务 = 多个仓储操作。
  5. 与 cybernetics 同构:Repository = 控制律的输入端口,DAO = 控制律的物理实现。

从名家方法论与工程化思路中蒸馏出自己的工程体系。