design-pattern
design-pattern/repository/crud-contract.md
medium
repository-crud-contract
Repository 只做 CRUD — find_by / save / delete,禁业务判断。Use when 写 Python 后端代码 / 评审涉及 `crud-contract` 的 PR。
repositoryrepodaofind_by禁业务判断
paths
**/repositories/**/*.py**/services/**/*.py
Repository · 使用规则
一句话定义
Repository = 持久化层的薄壳。只翻译"业务对象 ↔ 数据库行",不做任何业务决策。
必须满足的形态
# repositories/order_repo.py
class OrderRepo:
async def find_by_id(self, oid: UUID) -> Order | None: ...
async def find_by_owner(self, owner_id: UUID, *, limit: int = 20) -> list[Order]: ...
async def save(self, o: Order) -> Order: ...
async def delete(self, oid: UUID) -> None: ...
特征:方法名都是 find_* / save / delete / count_*,参数都是值类型或领域实体,返回也是领域实体或值。
禁止行为(命中 = 重构)
-
禁在 Repo 写业务判断
# ❌ async def find_active_orders(self, owner_id): items = await Order.filter(owner_id=owner_id) return [o for o in items if o.item_count >= 5 and o.channel_id] # 业务规则 -
禁 Repo 调用 Repo(除非父子聚合根)— 事务边界会模糊
-
禁返回 dict / tuple — 一律返回领域实体或值对象
-
禁抛业务异常 — 找不到记录返回
None,由 Service 决定抛NotFoundError
典型目录布局
<service>/repositories/— HTTP 主后端的 Repo<worker>/repositories/— 工作进程(种子脚本 / 后台任务)的 Repo
Service 与 Repo 的协作
class OrderService:
def __init__(self, repo: OrderRepo, channel_repo: ChannelRepo):
self._repo = repo
self._channel_repo = channel_repo
async def create_for_cart(self, cart: Cart, channel_id: UUID) -> Order:
# 1. 业务校验在 Service
channel = await self._channel_repo.find_by_id(channel_id)
if channel is None:
raise ChannelNotFoundError(channel_id) # 业务异常
# 2. 领域对象创建
o = Order.from_cart(cart, channel)
# 3. 持久化交给 Repo
return await self._repo.save(o)
自检
- [ ] Repo 方法名是
find_* / save / delete / count_*? - [ ] Repo 不写 if 业务判断?
- [ ] Repo 不调用其他 Repo(除非聚合根)?
- [ ] Repo 不抛业务异常?
- [ ] Repo 返回领域实体而非 dict?