You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

5.6 KiB

Project Repository 事务管理架构修复

日期: 2026-02-04
类型: 架构重构 + Bug 修复
影响范围: Repository 层、测试框架

问题背景

项目 Repository 层存在严重的架构设计问题:

  1. Repository 层错误地管理事务 - 所有方法都调用 await session.commit()
  2. 测试无法通过 - 与 conftest.py 的事务回滚机制冲突
  3. 违反单一职责原则 - Repository 应该只负责数据访问,不应该管理事务

错误示例

# ❌ 错误:Repository 不应该 commit
async def create(self, project: Project) -> Project:
    self.session.add(project)
    await self.session.commit()  # 错误!
    await self.session.refresh(project)
    return project

解决方案

1. Repository 层重构

将所有 commit() 改为 flush(),移除 refresh()

# ✅ 正确:Repository 只负责数据访问
async def create(self, project: Project) -> Project:
    self.session.add(project)
    await self.session.flush()  # 仅 flush,不 commit
    return project

修改的方法

  • create() - 创建项目
  • update() - 更新项目
  • move_to_trash() - 移至回收站
  • restore_from_trash() - 从回收站恢复
  • permanent_delete() - 永久删除
  • add_member() - 添加成员
  • remove_member() - 移除成员
  • update_member_role() - 更新成员角色
  • create_share() - 创建分享
  • delete_share() - 删除分享
  • move_to_folder() - 移动到文件夹

2. 测试重构

旧测试问题

  • 使用 sample_project fixture 在测试事务内创建数据
  • 导致 cannot use Connection.transaction() in a manually started transaction 错误

新测试模式(参考文件夹服务):

async def test_create_project(self, db_session):
    """测试创建项目"""
    repo = ProjectRepository(db_session)
    
    # 直接在测试中创建数据,不使用 fixture
    project = Project(
        name="新项目",
        type=ProjectType.MINE,
        owner_type="user",
        owner_id=UUID("00000000-0000-0000-0000-000000000001"),
        status=ProjectStatus.ACTIVE
    )
    
    created = await repo.create(project)
    
    assert created.id is not None
    assert created.name == "新项目"

测试结果

14/14 测试全部通过

tests/unit/repositories/test_project_repository.py::TestProjectRepository::test_create_project PASSED
tests/unit/repositories/test_project_repository.py::TestProjectRepository::test_get_by_id PASSED
tests/unit/repositories/test_project_repository.py::TestProjectRepository::test_get_by_id_not_found PASSED
tests/unit/repositories/test_project_repository.py::TestProjectRepository::test_update_project PASSED
tests/unit/repositories/test_project_repository.py::TestProjectRepository::test_move_to_trash PASSED
tests/unit/repositories/test_project_repository.py::TestProjectRepository::test_restore_from_trash PASSED
tests/unit/repositories/test_project_repository.py::TestProjectRepository::test_permanent_delete PASSED
tests/unit/repositories/test_project_repository.py::TestProjectRepository::test_get_by_user PASSED
tests/unit/repositories/test_project_repository.py::TestProjectRepository::test_exists_by_name PASSED
tests/unit/repositories/test_project_repository.py::TestProjectRepository::test_add_member PASSED
tests/unit/repositories/test_project_repository.py::TestProjectRepository::test_remove_member PASSED
tests/unit/repositories/test_project_repository.py::TestProjectRepository::test_create_share PASSED
tests/unit/repositories/test_project_repository.py::TestProjectRepository::test_get_share_by_token PASSED
tests/unit/repositories/test_project_repository.py::TestProjectRepository::test_delete_share PASSED

架构原则

Repository 层职责

应该做

  • 数据访问逻辑(CRUD)
  • 查询构建
  • 数据转换
  • 使用 flush() 确保数据可见性

不应该做

  • 调用 commit() 管理事务
  • 调用 refresh() 刷新对象(调用方负责)
  • 业务逻辑判断

Service 层职责

应该做

  • 事务管理(commit() / rollback()
  • 业务逻辑编排
  • 调用多个 Repository
  • 异常处理

测试最佳实践

应该做

  • 在测试方法内直接创建测试数据
  • 使用 db_session fixture(由 conftest 管理事务)
  • 依赖 conftest 的自动回滚机制

不应该做

  • 在 fixture 中创建需要 flush/commit 的数据
  • 在测试中手动 commit
  • 破坏 conftest 的事务管理

影响范围

需要更新的代码

  1. Service 层 - 需要添加 await session.commit()
  2. API 层 - 确保在请求结束时提交事务
  3. 其他 Repository - 检查是否有类似问题

示例:Service 层事务管理

class ProjectService:
    async def create_project(self, data: ProjectCreate) -> Project:
        try:
            project = Project(**data.dict())
            created = await self.repo.create(project)
            
            # Service 层负责提交事务
            await self.session.commit()
            
            return created
        except Exception as e:
            await self.session.rollback()
            raise

后续任务

  • 检查其他 Repository 是否有类似问题
  • 更新 Service 层添加事务管理
  • 创建 ADR 文档记录架构决策
  • 更新开发文档说明 Repository/Service 职责划分

参考

  • 文件夹服务测试:tests/unit/repositories/test_folder_share_repository.py
  • conftest 事务管理:server/tests/conftest.py
  • Repository 模式最佳实践:Martin Fowler - Patterns of Enterprise Application Architecture