refactor: finish transactional runtime migration

This commit is contained in:
jxxghp
2026-08-22 15:18:16 +08:00
parent fe2e6809f7
commit be18cace1f
59 changed files with 1006 additions and 568 deletions
+22 -1
View File
@@ -45,6 +45,10 @@ class CleanupRepository(Protocol):
"""返回一次维护运行共用的数据库会话上下文。"""
...
def unit_of_work(self, db: Any) -> "CleanupUnitOfWork":
"""返回绑定到当前维护会话的事务边界。"""
...
def delete_messages(self, db: Any, cutoff: str, limit: int) -> int:
"""删除早于截止时间的消息。"""
...
@@ -70,6 +74,18 @@ class CleanupRepository(Protocol):
...
class CleanupUnitOfWork(Protocol):
"""数据维护每一批删除所需的最小事务能力。"""
def commit(self) -> None:
"""提交当前批次。"""
...
def rollback(self) -> None:
"""回滚失败批次并恢复会话可用状态。"""
...
class DataCleanupService:
"""按配置执行分批数据清理并生成兼容报告。"""
@@ -175,16 +191,19 @@ class DataCleanupService:
value=plan_index / total_plans * 100,
text=f"正在清理数据表 {plan.name} ...",
)
unit_of_work = self._repository.unit_of_work(db)
table_report = self._cleanup_in_batches(
db=db,
table_name=plan.name,
delete_batch=plan.delete_batch,
unit_of_work=unit_of_work,
)
table_report["cutoff"] = plan.cutoff
table_report["retention_days"] = plan.retention_days
report["tables"][plan.name] = table_report
report["total_deleted"] += table_report["deleted"]
except Exception as err:
self._repository.unit_of_work(db).rollback()
errors.append(f"{plan.name}: {str(err)}")
logger.error(f"数据表 {plan.name} 清理失败:{str(err)}")
report["tables"][plan.name] = {
@@ -279,12 +298,13 @@ class DataCleanupService:
),
]
@staticmethod
def _cleanup_in_batches(
self,
*,
db: Any,
table_name: str,
delete_batch: Callable[[Any], int],
unit_of_work: CleanupUnitOfWork,
) -> Dict[str, int]:
"""循环执行单表分批删除,直到持久化端口返回零。"""
total_deleted = 0
@@ -293,6 +313,7 @@ class DataCleanupService:
deleted = delete_batch(db) or 0
if deleted <= 0:
break
unit_of_work.commit()
batches += 1
total_deleted += deleted
logger.info(
+4 -1
View File
@@ -146,14 +146,16 @@ class OutboxDispatcher:
lease_seconds: int = 60,
clock: Callable[[], datetime] | None = None,
close: Callable[[], None] | None = None,
failure_observer: Callable[[bool], None] | None = None,
) -> None:
"""注入持久端口、topic handler有界重试策略。"""
"""注入持久端口、topic handler有界重试策略与失败观测端口"""
self._repository = repository
self._handlers = handlers
self._max_attempts = max_attempts
self._lease_seconds = lease_seconds
self._clock = clock or (lambda: datetime.now(timezone.utc))
self._close = close or (lambda: None)
self._failure_observer = failure_observer or (lambda _dead: None)
def dispatch_one(self) -> bool:
"""处理一条到期消息;无消息返回 False,handler 失败留待重试。"""
@@ -176,6 +178,7 @@ class OutboxDispatcher:
last_error=str(error)[:4000],
dead=dead,
)
self._failure_observer(dead)
return True
self._repository.complete(message.message_id, now)
return True
+38 -7
View File
@@ -4,7 +4,7 @@
避免 API 层同时承担 HTTP 编排和 ORM 适配职责。
"""
from collections.abc import Callable
from collections.abc import Awaitable, Callable
from typing import Any, Protocol
@@ -33,12 +33,27 @@ class UserRepository(Protocol):
"""更新用户 OTP 状态。"""
class AsyncUnitOfWork(Protocol):
"""用户写用例所需的异步事务边界。"""
async def commit(self) -> None:
"""提交用户写入。"""
async def rollback(self) -> None:
"""回滚失败的用户写入。"""
class UserService:
"""用户管理应用服务。"""
def __init__(self, repository: UserRepository) -> None:
"""创建用户服务。"""
def __init__(
self,
repository: UserRepository,
unit_of_work: AsyncUnitOfWork | None = None,
) -> None:
"""创建用户服务;旧独立仓储可暂不提供请求级 UoW。"""
self._repository = repository
self._unit_of_work = unit_of_work
async def list(self) -> list[Any]:
"""返回用户列表。"""
@@ -54,19 +69,35 @@ class UserService:
async def create(self, payload: dict[str, Any]) -> Any | None:
"""创建用户。"""
return await self._repository.async_create(payload)
return await self._write(lambda: self._repository.async_create(payload))
async def update(self, user_id: int, payload: dict[str, Any]) -> Any | None:
"""更新用户。"""
return await self._repository.async_update(user_id, payload)
return await self._write(
lambda: self._repository.async_update(user_id, payload)
)
async def delete(self, user_id: int) -> None:
"""删除用户。"""
await self._repository.async_delete(user_id)
await self._write(lambda: self._repository.async_delete(user_id))
async def update_otp(self, name: str, otp: bool, secret: str) -> None:
"""更新用户 OTP 状态。"""
await self._repository.async_update_otp_by_name(name, otp, secret)
await self._write(
lambda: self._repository.async_update_otp_by_name(name, otp, secret)
)
async def _write(self, operation: Callable[[], Awaitable[Any]]) -> Any:
"""执行用户写入,并在正式请求路径统一提交或回滚。"""
try:
result = await operation()
if self._unit_of_work is not None:
await self._unit_of_work.commit()
return result
except Exception:
if self._unit_of_work is not None:
await self._unit_of_work.rollback()
raise
_configured_user_id_lookup: Callable[[int], Any | None] | None = None