Files
portfolio-hub/docs/batches/bat-006_20260511_portfolio-uuid-migration/design.md
T
genichin 829629fa8f feat: implement portfolio UUID migration and v1 API finalization (BAT-006, BAT-001 re-verified)
Core Changes:
- Migrate portfolio_id from string to UUID in schema and repository layer
- Update portfolio CLI commands to use name+user combination for identification
- Update HTTP API to accept portfolio_id as UUID path parameter
- Implement portfolio ownership validation in repository queries

Requirements Completed (REQ-016 to REQ-019):
- REQ-016: portfolios table UUID schema migration with data migration script
- REQ-017: CLI portfolio commands with name+user identification
- REQ-018: HTTP API portfolio path parameters and v1 asset_id UUID returns
- REQ-019: Portfolio UUID baseline documentation updates

BAT-001 Re-verification:
- REQ-004~010 marked as Implemented after verification evidence validation
- All v1 API endpoints verified with comprehensive test coverage

Documentation Updates:
- Update data-model.md with portfolio UUID structure
- Update interface-contract.md with UUID path parameters
- Update runtime-flows.md with ownership-based resolution flows
- Update project-structure.md with database layer changes
- Add migration script for portfolio UUID transition

Testing:
- Add test_portfolio_uuid.py for migration validation
- Update test_cli.py and test_portfolio_ownership.py for ownership checks
2026-05-11 09:27:57 +00:00

6.0 KiB

BAT-006 Design

  • Status: confirmed
  • Batch: BAT-006

Architecture Summary

portfolios.portfolio_id를 사용자 지정 TEXT PK에서 서버 생성 UUID(TEXT)로 교체한다. 기존 portfolio_id 문자열은 name 컬럼으로 이전하고, (owner_user_id, name) 복합 UNIQUE 제약을 DB 레벨에서 도입해 사용자 범위 내 이름 유일성을 보장한다. CLI는 <name> + --user 조합으로 portfolio를 식별하고 내부에서 get_portfolio_by_name_and_user()를 통해 UUID를 조회한다. HTTP API path parameter와 GET /v1/assets asset_id 반환은 UUID 기반으로 전환된다.

Baseline vs Batch 변경점

docs/project-structure.md (현재 baseline)

  • app/repositories/postgres.py: SQL 세부사항 담당으로만 기술 — get_portfolio_by_name_and_user() 미기재
  • app/domain/models.py: portfolio_id 필드 의미 변경 미기재

docs/runtime-flows.md (현재 baseline)

  • CLI Operational Command Execution flow: name+user 식별 방식 미기재
  • v1 API — GET /v1/assets flow: asset_id 필드 언급 없음

docs/data-model.md (현재 baseline)

  • portfolio_id :: text :: stable portfolio primary key — UUID 의미 변경 미기재
  • name 컬럼 별도 기재 없음
  • (owner_user_id, name) UNIQUE 제약 미기재

docs/interface-contract.md (현재 baseline)

  • GET /v1/assets 응답 명세 없음 (bat-001에서 추가 예정이었으나 아직 반영 안 됨)
  • trex-basic 동시 배포 계획 없음

Changed Areas

  • Docs:
    • docs/project-structure.md
    • docs/runtime-flows.md
    • docs/data-model.md
    • docs/interface-contract.md
    • docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md (A-3 철회 주석)
  • Modules:
    • migrations/versions/20260511_0015_portfolio_uuid_migration.py (신규 Alembic revision)
    • app/domain/models.py (주석 없음, Portfolio dataclass 구조 변경 없음 — 기존 portfolio_id + name 필드 유지)
    • app/repositories/postgres.py:
      • create_portfolio(): portfolio_id 파라미터 제거, 서버 UUID 생성
      • get_portfolio_by_name_and_user() 신규 메서드
      • ensure_schema(): (owner_user_id, name) UNIQUE 제약 추가
      • update_portfolio(): UniqueViolation 시 ValueError("이미 사용 중인 이름입니다") 반환
    • app/domain/services.py: PortfolioDomainRepository Protocol 업데이트
    • app/schemas/portfolio.py:
      • CreatePortfolioRequest: portfolio_id 필드 제거, name: str 필수로 변경, name 유효성 validator 추가
    • app/services/portfolio_service.py: create_portfolio() 호출부 업데이트
    • app/cli/commands/portfolio.py:
      • create: portfolio_id Argument → name Argument (필수), --name Option 제거
      • status, buy, sell, invest, transfer, reset, delete: portfolio_id → name Argument + --user Option
      • update: portfolio_id → name Argument + --user Option + --new-name Option (이름 변경용)
      • _resolve_portfolio_id() helper 추가
      • _validate_portfolio_name() helper 추가
  • Interfaces:
    • CLI: portfolio <subcommand> <portfolio_id> → portfolio <subcommand> <name> --user <username>
    • HTTP API: POST /portfolios request body: portfolio_id 필드 제거, name 필수화
    • HTTP API: GET /v1/assets assets[].asset_id → UUID 형식 반환

Key Decisions

  1. portfolio_id 컬럼 타입 TEXT 유지: PostgreSQL UUID 타입 변환은 이번 범위 밖. uuid.uuid4() 문자열을 TEXT로 저장.
  2. get_portfolio_by_name_and_user() 공유: CLI와 HTTP API 식별 모두 이 단일 repository 메서드 사용. Protocol에도 추가.
  3. 이름 대소문자 구분: TV와 tv는 다른 이름. DB UNIQUE 제약도 case-sensitive.
  4. 단일 Alembic revision: name 컬럼 추가 (IF NOT EXISTS), UUID 교체, FK 4개 테이블 갱신, UNIQUE 제약을 하나의 revision에서 수행. IF NOT EXISTS 가드로 재실행 안전성 보장.
  5. update 명령 name 변경: CLI update 명령은 --new-name Option으로 이름 변경. --force 없으면 confirm 프롬프트. DB UNIQUE 위반 시 이미 사용 중인 이름입니다: {name} 오류.
  6. name 유효성: [a-zA-Z0-9._:-], 1~100자. Pydantic validator + CLI 레이어에서 이중 검사.
  7. comment/tag sub-commands: REQ-017 명시 범위 밖. 이번 batch에서 변경하지 않음 (UUID 직접 전달 필요).
  8. dcy-002 A-3 공식 철회: REQ-018 구현 시 해당 discovery 문서에 주석 추가.

Edge Cases

  • 기존 portfolio_id 값이 UUID 형식인 경우: migration이 이미 적용된 것으로 판단하는 guard (UNIQUE constraint 존재 여부로 판단). 재실행 시 skip.
  • FK 고아 레코드: migration revision 내 트랜잭션 후 orphan 검증 쿼리. 고아 발견 시 RuntimeError 발생.
  • name 허용 문자 범위 외 입력: CLI validation + Pydantic validator에서 조기 거부.
  • 동일 owner_user_id + 동일 name: DB UNIQUE 위반 → create_portfolio()에서 ValueError, update_portfolio()에서 UniqueViolation catch → ValueError.
  • 존재하지 않는 name+user 조합: get_portfolio_by_name_and_user() None 반환 → 포트폴리오를 찾을 수 없습니다: {name} (user: {username}).
  • revision 재실행: already_migrated guard (UNIQUE constraint 유무 확인). 이미 적용됐으면 UUID 교체 블록 전체 skip.
  • migration 중 portfolio_tags PK 업데이트: PostgreSQL은 PK 값 UPDATE 허용. FK 제약 없으므로 안전.

Architecture Impact

both (project-structure + runtime-flows)

Reference Doc Update Plan

  • project-structure.md:
    • app/repositories/postgres.py 항목: get_portfolio_by_name_and_user(owner_user_id, name) → Portfolio | None 추가
    • app/domain/models.py 항목: Portfolio.portfolio_id가 서버 생성 UUID임을 명시
  • runtime-flows.md:
    • CLI Operational Command Execution flow: <name> + --user → get_portfolio_by_name_and_user() → UUID 조회 흐름 추가
    • v1 API — GET /v1/assets flow: asset_id = portfolio_id (UUID) 반환 명시