Skip to content

Repository Factory(仓储 + 工厂) ​

解决什么问题:聚合的"加载"与"创建"是两个不同的入口,但都涉及不变量保证——Repository 负责从持久化恢复完整聚合(不变量从磁盘重建),Factory 负责从零创建聚合(不变量从零建立)。两者必须协作。

它是什么 ​

Repository(仓储)是聚合根的持久化抽象——通过 ID 找到聚合,封装持久化技术细节(SQL / NoSQL / 文件)。 Factory(工厂)是聚合根的创建抽象——封装"从无到有"的业务规则。

两者结合 = Repository.findById() 返回的永远是"不变量已满足"的聚合:

python
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)都重建不变量。

关键属性 ​

属性RepositoryFactory
职责持久化抽象 + 重建不变量创建 + 建立不变量
输入聚合根 ID业务参数(无 ID)
输出完整聚合根(含所有内部对象)完整聚合根(已发布事件)
调用时机查询 / 重建新建 / 跨聚合组装
依赖持久化层(DB / Cache)业务校验 + ID 生成器
反模式返回部分聚合(懒加载破坏不变量)客户端 new() 后到处初始化
核心约束永远返回不变量已满足的聚合永远返回不变量已满足的聚合

怎么用 ​

场景 1:聚合根的 CRUD(标准场景) ​

python
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 路径) ​

python
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"。

python
# 业务层
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)。

常见误区 ​

  1. "Repository = DAO"——错。DAO 是技术细节(SQL 封装),Repository 是业务语义(聚合根的存取)。详见 Repository vs DAO
  2. "Repository 可以返回部分聚合"——错。Repository 必须返回完整聚合——懒加载破坏不变量。聚合根的不变量要求所有内部对象都在
  3. "Factory 和 Repository 是同一个东西"——错。一个是"创建路径",一个是"加载路径",但目的相同:返回不变量已满足的聚合
  4. "Repository 直接接受 SQL"——错。Repository 接受的是业务对象(聚合根),业务层不应该出现 SQL
  5. "Repository 跨聚合"——错。每个 Repository 只服务一个聚合根类型。跨聚合查询用 CQRS

关键洞察 ​

Repository + Factory = 聚合生命周期的"不变量双保险"。

  • Factory 在创建时建立不变量(从零到有)
  • Repository 在加载时重建不变量(从磁盘到内存)

两者都承诺:客户端拿到的对象,永远是合法的。

这就是 V2.7 B4 抽象 + 控制论 Ch 4 §4.6 不变量的工程化——抽象边界 = 不变量边界。 客户端不需要知道怎么保证不变量,只需要调用 Repository / Factory。

相关链接 ​

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