refactor: Sprint 3 - 引入 UnitOfWork + Repository 架构

新增:
- src/db/unit_of_work.py: UnitOfWork 事务封装
- src/db/repositories.py: Match/Team/League/Prediction Repository

重构:
- 删除 src/data/match_lookup.py(由 Repository 替代)
- 数据源(bzzoiro/understat/injuries)不再自行 commit
- API 路由(ingest)改用 UnitOfWork
- LLM 服务(predict/orchestrator/eval/backtest)改用 UnitOfWork

事务边界统一由调用方控制,数据层不再自行决定 commit。
This commit is contained in:
shangfangjian
2026-09-15 00:42:05 +08:00
parent cb36dc3ef9
commit 483cb956ba
11 changed files with 281 additions and 117 deletions
+63
View File
@@ -0,0 +1,63 @@
"""工作单元(Unit of Work):统一事务边界。
使用方式:
async with UnitOfWork(db) as uow:
await uow.matches.get_by_id(1)
await uow.matches.add(new_match)
# 退出时自动 commit,异常时 rollback
"""
from __future__ import annotations
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import AsyncGenerator
from sqlalchemy.ext.asyncio import AsyncSession
from src.db.base import AsyncSessionLocal
class UnitOfWork:
"""工作单元:封装事务边界。"""
def __init__(self, session: AsyncSession) -> None:
self._session = session
self.committed = False
@property
def session(self) -> AsyncSession:
return self._session
async def commit(self) -> None:
await self._session.commit()
self.committed = True
async def rollback(self) -> None:
await self._session.rollback()
async def close(self) -> None:
await self._session.close()
async def __aenter__(self) -> "UnitOfWork":
return self
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
if exc_type is not None:
await self.rollback()
await self.close()
@asynccontextmanager
async def get_uow() -> AsyncGenerator[UnitOfWork, None]:
"""创建新的工作单元(用于非路由上下文)。"""
session = AsyncSessionLocal()
uow = UnitOfWork(session)
try:
yield uow
if not uow.committed:
await uow.commit()
except Exception:
await uow.rollback()
raise
finally:
await uow.close()