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
6.0 KiB
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 Executionflow: name+user 식별 방식 미기재v1 API — GET /v1/assetsflow: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.mddocs/runtime-flows.mddocs/data-model.mddocs/interface-contract.mddocs/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:PortfolioDomainRepositoryProtocol 업데이트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_idArgument →nameArgument (필수),--nameOption 제거status,buy,sell,invest,transfer,reset,delete:portfolio_id→nameArgument +--userOptionupdate:portfolio_id→nameArgument +--userOption +--new-nameOption (이름 변경용)_resolve_portfolio_id()helper 추가_validate_portfolio_name()helper 추가
- Interfaces:
- CLI:
portfolio <subcommand> <portfolio_id>→portfolio <subcommand> <name> --user <username> - HTTP API:
POST /portfoliosrequest body:portfolio_id필드 제거,name필수화 - HTTP API:
GET /v1/assetsassets[].asset_id→ UUID 형식 반환
- CLI:
Key Decisions
- portfolio_id 컬럼 타입 TEXT 유지: PostgreSQL UUID 타입 변환은 이번 범위 밖.
uuid.uuid4()문자열을 TEXT로 저장. - get_portfolio_by_name_and_user() 공유: CLI와 HTTP API 식별 모두 이 단일 repository 메서드 사용. Protocol에도 추가.
- 이름 대소문자 구분:
TV와tv는 다른 이름. DB UNIQUE 제약도 case-sensitive. - 단일 Alembic revision: name 컬럼 추가 (IF NOT EXISTS), UUID 교체, FK 4개 테이블 갱신, UNIQUE 제약을 하나의 revision에서 수행. IF NOT EXISTS 가드로 재실행 안전성 보장.
- update 명령 name 변경: CLI
update명령은--new-nameOption으로 이름 변경.--force없으면 confirm 프롬프트. DB UNIQUE 위반 시이미 사용 중인 이름입니다: {name}오류. - name 유효성:
[a-zA-Z0-9._:-], 1~100자. Pydantic validator + CLI 레이어에서 이중 검사. - comment/tag sub-commands: REQ-017 명시 범위 밖. 이번 batch에서 변경하지 않음 (UUID 직접 전달 필요).
- 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_migratedguard (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 Executionflow:<name>+--user→get_portfolio_by_name_and_user()→ UUID 조회 흐름 추가v1 API — GET /v1/assetsflow:asset_id = portfolio_id (UUID)반환 명시