Repository Factory(仓储 + 工厂)
解决什么问题:聚合的"加载"与"创建"是两个不同的入口,但都涉及不变量保证——Repository 负责从持久化恢复完整聚合(不变量从磁盘重建),Factory 负责从零创建聚合(不变量从零建立)。两者必须协作。
它是什么
Repository(仓储)是聚合根的持久化抽象——通过 ID 找到聚合,封装持久化技术细节(SQL / NoSQL / 文件)。 Factory(工厂)是聚合根的创建抽象——封装"从无到有"的业务规则。
两者结合 = Repository.findById() 返回的永远是"不变量已满足"的聚合:
order = OrderRepository.findById(order_id)
# 这里的 order 保证:所有订单项加载完毕、状态合法、不变量满足
# 这是 Repository 在加载时调用的"重建工厂"完成的Vernon Ch 12: "The Repository treats the AGGREGATE as a whole. The only objects the client should have to deal with are the AGGREGATE roots."
V2 视角(B4 抽象 + 控制论 Ch 4 §4.6 不变量):Repository + Factory = 不变量在生命周期全程的保障机制——创建时(Factory)+ 加载时(Repository.findById)都重建不变量。
关键属性
| 属性 | Repository | Factory |
|---|---|---|
| 职责 | 持久化抽象 + 重建不变量 | 创建 + 建立不变量 |
| 输入 | 聚合根 ID | 业务参数(无 ID) |
| 输出 | 完整聚合根(含所有内部对象) | 完整聚合根(已发布事件) |
| 调用时机 | 查询 / 重建 | 新建 / 跨聚合组装 |
| 依赖 | 持久化层(DB / Cache) | 业务校验 + ID 生成器 |
| 反模式 | 返回部分聚合(懒加载破坏不变量) | 客户端 new() 后到处初始化 |
| 核心约束 | 永远返回不变量已满足的聚合 | 永远返回不变量已满足的聚合 |
怎么用
场景 1:聚合根的 CRUD(标准场景)
class OrderRepository:
def find_by_id(self, order_id: OrderId) -> Order:
# 1. 从 DB 加载聚合根 + 所有内部对象
row = self.db.query("SELECT * FROM orders WHERE id = %s", order_id)
items = self.db.query("SELECT * FROM order_items WHERE order_id = %s", order_id)
# 2. 重建聚合(Factory 重建路径)
order = Order.reconstitute(order_id, row.status, items)
# 3. 返回完整聚合——不变量已满足
return order
def save(self, order: Order) -> None:
# 1. 提取事件
events = order.collect_events()
# 2. 持久化聚合(聚合根 + 所有内部对象)
self.db.execute("UPDATE orders ...", order)
# 3. 发布事件
for event in events:
self.event_publisher.publish(event)场景 2:聚合根的创建(Factory 路径)
class Order:
@classmethod
def create(cls, customer_id, items) -> Order:
# Factory:建立不变量
order_id = OrderId.generate()
order = cls(order_id, customer_id, items, "NEW", now())
order.add_event(OrderCreated(order_id, customer_id))
return order
# reconstitute 是 Repository 使用的"重建工厂"
@classmethod
def reconstitute(cls, order_id, status, items) -> Order:
# Repository 重建路径:不发新事件
return cls(order_id, None, items, status, None, _events=[])场景 3:Repository 隔离持久化技术
价值:业务代码不知道底层是 MySQL 还是 MongoDB,只知道"通过 ID 找 Order"。
# 业务层
order = order_repo.find_by_id(order_id) # 不知道 DB
# 测试层(替换为内存实现)
class InMemoryOrderRepository:
def __init__(self): self._store = {}
def find_by_id(self, order_id):
return self._store.get(order_id)场景 4:跨聚合查询(绕过 Repository)
警告:跨聚合的复杂查询(如 join 多个聚合)不应该用 Repository——Repository 只能查单个聚合。
处理:用 CQRS 读模型绕过 Repository,直接走专门优化的查询(详见 CQRS-ES)。
常见误区
- "Repository = DAO"——错。DAO 是技术细节(SQL 封装),Repository 是业务语义(聚合根的存取)。详见 Repository vs DAO
- "Repository 可以返回部分聚合"——错。Repository 必须返回完整聚合——懒加载破坏不变量。聚合根的不变量要求所有内部对象都在
- "Factory 和 Repository 是同一个东西"——错。一个是"创建路径",一个是"加载路径",但目的相同:返回不变量已满足的聚合
- "Repository 直接接受 SQL"——错。Repository 接受的是业务对象(聚合根),业务层不应该出现 SQL
- "Repository 跨聚合"——错。每个 Repository 只服务一个聚合根类型。跨聚合查询用 CQRS
关键洞察
Repository + Factory = 聚合生命周期的"不变量双保险"。
- Factory 在创建时建立不变量(从零到有)
- Repository 在加载时重建不变量(从磁盘到内存)
两者都承诺:客户端拿到的对象,永远是合法的。
这就是 V2.7 B4 抽象 + 控制论 Ch 4 §4.6 不变量的工程化——抽象边界 = 不变量边界。 客户端不需要知道怎么保证不变量,只需要调用 Repository / Factory。
相关链接
- Aggregate——Repository 服务的对象
- Factory Invariants——Factory 的不变量建立机制
- Repository vs DAO——为什么 Repository 不是 DAO
- [V3 涌现作为] · 抽象——抽象边界 = 不变量边界
- 源典 IDDD Ch 12——Vernon 原书精读
- 源典 控制论 Ch 4 §4.6——不变量数学原型