refactor(api): TOML 配置 SSOT、统一错误契约、Auth/事务加固与可观测性 (#33)
配置 SSOT(TOML + .env) 统一错误契约 Auth 与事务边界 Redis / Celery 可靠性:业务 Redis(DB/0)与 Celery broker/backend(DB/1)显式拆分;连接池、sync client 可观测性(OpenTelemetry + LGTM)
This commit is contained in:
@@ -6,16 +6,32 @@
|
||||
|
||||
事务规则:
|
||||
- get_async_db() 只负责创建和关闭 session,不自动 commit/rollback。
|
||||
- 事务提交由 service 层显式调用 await db.commit()。
|
||||
- service / Celery task 层优先使用 transactional() / transactional_sync() 管理多步写操作。
|
||||
- repo 层禁止调用 commit() / rollback()。
|
||||
|
||||
transactional 语义:
|
||||
- transactional() / transactional_sync() 是顶层事务边界;成功 exit 时 commit 整个 session,异常时 rollback 整个 session。
|
||||
- 不支持嵌套自身:同一 session 上连续两次 transactional() = 两次独立 commit(WS pipeline 分段持久化属于此模式)。
|
||||
- 需要嵌套回滚时:在已开启的事务内使用 transactional_nested() / transactional_nested_sync()(基于 SQLAlchemy begin_nested() savepoint)。
|
||||
- 选择指南:单步/整段业务原子提交 → transactional;长生命周期 session 内局部试错、可独立回滚的子步骤 → transactional_nested(必须在外层事务 active 期间)。
|
||||
|
||||
transactional_nested 示例(外层提交、内层失败仅回滚 savepoint)::
|
||||
|
||||
async with transactional(session):
|
||||
session.add(parent_row)
|
||||
try:
|
||||
async with transactional_nested(session):
|
||||
await attempt_optional_side_effect(session)
|
||||
except RecoverableError:
|
||||
pass # savepoint rolled back; parent_row still commits
|
||||
"""
|
||||
|
||||
from contextlib import contextmanager
|
||||
from contextlib import asynccontextmanager, contextmanager
|
||||
from typing import AsyncGenerator
|
||||
|
||||
from sqlalchemy import create_engine, text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||
from sqlalchemy.orm import DeclarativeBase, sessionmaker
|
||||
from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
@@ -69,6 +85,32 @@ async def get_async_db() -> AsyncGenerator[AsyncSession, None]:
|
||||
await session.close()
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def transactional(session: AsyncSession):
|
||||
"""Top-level async transaction: commit on success, rollback on any exception.
|
||||
|
||||
Do not nest transactional() on the same session; each call commits independently.
|
||||
For partial rollback within an active transaction, use transactional_nested().
|
||||
"""
|
||||
try:
|
||||
yield session
|
||||
await session.commit()
|
||||
except Exception:
|
||||
await session.rollback()
|
||||
raise
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def transactional_nested(session: AsyncSession):
|
||||
"""Savepoint boundary; roll back only this block on error.
|
||||
|
||||
Must be used while the session already has an active transaction (e.g. inside
|
||||
transactional() before it commits, or after autobegin from a prior write).
|
||||
"""
|
||||
async with session.begin_nested():
|
||||
yield session
|
||||
|
||||
|
||||
# ── Sync engine & session (Celery, Alembic, scripts) ─────────
|
||||
|
||||
sync_engine = create_engine(
|
||||
@@ -100,6 +142,28 @@ def init_db_schema() -> None:
|
||||
Base.metadata.create_all(bind=sync_engine)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def transactional_sync(session: Session):
|
||||
"""Top-level sync transaction: commit on success, rollback on any exception.
|
||||
|
||||
Do not nest transactional_sync() on the same session; each call commits independently.
|
||||
For partial rollback within an active transaction, use transactional_nested_sync().
|
||||
"""
|
||||
try:
|
||||
yield session
|
||||
session.commit()
|
||||
except Exception:
|
||||
session.rollback()
|
||||
raise
|
||||
|
||||
|
||||
@contextmanager
|
||||
def transactional_nested_sync(session: Session):
|
||||
"""Savepoint boundary for sync Celery / scripts; roll back only this block on error."""
|
||||
with session.begin_nested():
|
||||
yield session
|
||||
|
||||
|
||||
@contextmanager
|
||||
def get_sync_db():
|
||||
"""Context-managed synchronous session for Celery tasks."""
|
||||
|
||||
Reference in New Issue
Block a user