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
116 lines
7.0 KiB
Markdown
116 lines
7.0 KiB
Markdown
# BAT-007 Design
|
|
|
|
- Status: confirmed
|
|
- Batch: BAT-007
|
|
- Profile: batch-lite
|
|
|
|
## Architecture Summary
|
|
|
|
이 batch는 코드 구조 변경 없이 분석 및 문서화만 수행한다. 주요 설계 작업은 `ensure_schema()`와 Alembic revision 간 schema ownership 경계를 코드에서 직접 분석하고 그 결과를 baseline 문서에 반영하는 것이다.
|
|
|
|
코드 변경이 필요한 경우는 분석 결과 ownership 충돌이 발견될 때뿐이며, 이 batch 범위에서는 해당 충돌이 기존 IF NOT EXISTS/stamp 메커니즘으로 이미 처리되고 있음이 확인되었다.
|
|
|
|
## Changed Areas
|
|
|
|
| 대상 | 변경 유형 | REQ |
|
|
|------|-----------|-----|
|
|
| `README.md` | 로컬 DB 검증 절차, runbook 추가 | REQ-001, REQ-003 |
|
|
| `docs/runtime-flows.md` | Local DB 초기화 흐름, Alembic/ensure_schema() 경계 흐름 추가 | REQ-001, REQ-002, REQ-003 |
|
|
| `docs/project-structure.md` | Schema ownership 경계 섹션 추가 | REQ-002, REQ-003 |
|
|
| `docker-compose.local-test.yml` | 변경 없음 — 현행 구성이 검증 시나리오 요건 충족 | — |
|
|
| `app/cli/commands/health.py` | 변경 없음 — init-db/migrate-db/health-check 출력 구조 확인 완료 | — |
|
|
| `app/db/migrations.py` | 변경 없음 — bootstrap_or_upgrade_to_head() 흐름 분석으로 coverage 충족 | — |
|
|
| `app/repositories/postgres.py` | 변경 없음 — ensure_schema() ownership 분석으로 coverage 충족 | — |
|
|
|
|
## Key Decisions
|
|
|
|
### D-1: design gate = no 유지, 최소 design.md 작성
|
|
|
|
Planning의 `Design Gate: no` 결정을 유지한다. 사용자가 draft-batch-design을 명시적으로 요청했으므로 이 문서를 생성하되, 신규 인터페이스/코드 구조 결정 사항이 없음을 확인하고 기록하는 용도로 사용한다.
|
|
|
|
### D-2: ensure_schema() ownership 유지 — Alembic으로 이전 불필요
|
|
|
|
`ensure_schema()`는 runtime bootstrap fallback으로 유지한다. fresh DB → app 시작 → migrate-db 순서에서 `bootstrap_or_upgrade_to_head()`의 legacy stamp 경로가 이 충돌을 처리한다. 이번 batch에서 ensure_schema()를 제거하거나 Alembic으로 완전 이전하는 작업은 범위 밖이다.
|
|
|
|
### D-3: 검증 시나리오를 README.md + docs/runtime-flows.md에 배분
|
|
|
|
검증 매트릭스(REQ-001)를 별도 파일로 생성하지 않고, 운영자가 참조하는 README.md와 기술적 흐름을 담는 docs/runtime-flows.md에 나누어 반영한다. 이 방식이 docs/project-structure.md 정책(docs/ = SDLC source-of-truth)과 일관된다.
|
|
|
|
## Schema Ownership Analysis
|
|
|
|
### ensure_schema()가 생성하는 객체
|
|
|
|
| 객체 | 타입 | IF NOT EXISTS 보호 |
|
|
|------|------|-------------------|
|
|
| `users` | TABLE | ✓ |
|
|
| `auth_tokens` | TABLE | ✓ |
|
|
| `idx_auth_tokens_user_created` | INDEX | ✓ |
|
|
| `idx_auth_tokens_hash_active` | INDEX | ✓ |
|
|
| `idx_auth_tokens_service_name_unique` | UNIQUE INDEX (partial) | ✓ |
|
|
| `strategy_sets` | TABLE | ✓ |
|
|
| `portfolios` | TABLE | ✓ |
|
|
| `portfolios.name` | COLUMN | ADD COLUMN IF NOT EXISTS |
|
|
| `portfolios.owner_user_id` | COLUMN | ADD COLUMN IF NOT EXISTS |
|
|
| `uq_portfolios_owner_name` | UNIQUE CONSTRAINT | DO $$...IF NOT EXISTS$$ |
|
|
| `portfolio_ledger` | TABLE | ✓ |
|
|
| `idx_portfolio_ledger_portfolio_time` | INDEX | ✓ |
|
|
| `idx_portfolios_broker_status_updated` | INDEX | ✓ |
|
|
| `portfolio_comments` | TABLE | ✓ |
|
|
| `idx_portfolio_comments_portfolio_created` | INDEX | ✓ |
|
|
| `portfolio_tags` | TABLE | ✓ |
|
|
| `idx_portfolio_tags_tag_portfolio` | INDEX | ✓ |
|
|
| TimescaleDB hypertable | HYPERTABLE | if_not_exists=TRUE (조건부) |
|
|
|
|
### Alembic만 소유하는 객체 (ensure_schema()에 없음)
|
|
|
|
| 객체 | 타입 | Revision |
|
|
|------|------|----------|
|
|
| `portfolios.market` | COLUMN | 0014 |
|
|
| `auth_tokens.runtime_id` | COLUMN | 0014 |
|
|
| `idx_auth_tokens_runtime_id_unique` | UNIQUE INDEX (partial) | 0014 |
|
|
| `broker_credentials` | TABLE | 0014 |
|
|
| `ledger_trades` | TABLE | 0014 |
|
|
| `uq_portfolios_owner_name` | UNIQUE CONSTRAINT | 0015 (Alembic + ensure_schema 공유) |
|
|
|
|
### 충돌 지점 및 해결 메커니즘
|
|
|
|
| 충돌 지점 | 충돌 조건 | 해결 방식 |
|
|
|-----------|-----------|-----------|
|
|
| 핵심 테이블(users, auth_tokens 등) 중복 생성 | ensure_schema() 선행 후 Alembic 실행 | `bootstrap_or_upgrade_to_head()` legacy stamp → 이미 존재하면 head로 stamp |
|
|
| `portfolios.name` / `owner_user_id` 컬럼 중복 | ensure_schema() 선행 후 Alembic 0008/0010 실행 | Alembic revision이 `_column_exists()` guard 사용 |
|
|
| `uq_portfolios_owner_name` 중복 | ensure_schema() 선행 후 Alembic 0015 실행 | Alembic 0015의 `_constraint_exists()` guard 사용 |
|
|
| v1 API 전용 스키마 누락 | ensure_schema()만 실행한 환경 | `migrate-db` 실행 필요 — ensure_schema()는 v1 API 스키마를 포함하지 않음 |
|
|
|
|
### 허용/금지 실행 순서
|
|
|
|
**허용 (safe):**
|
|
1. `init-db` → App 시작 → 정상 운영 (Alembic이 완전 스키마 생성, ensure_schema()는 no-op)
|
|
2. App 시작 (ensure_schema() 선행) → `init-db` 또는 `migrate-db` (legacy stamp + upgrade 경로)
|
|
3. 이미 Alembic 관리 DB → `migrate-db` (증분 upgrade)
|
|
4. 검증 후 `migrate-db` 재실행 (모든 revision이 IF NOT EXISTS 보호)
|
|
|
|
**주의 (caution):**
|
|
1. ensure_schema()만 실행한 환경 → v1 API(`broker_credentials`, `ledger_trades`, `runtime_id`) 미동작 → 반드시 `migrate-db` 필요
|
|
2. `migrate-db` 단독 실행 → DB 미존재 시 실패 → `init-db` 우선 실행 필요
|
|
|
|
**금지 (forbidden):**
|
|
1. `alembic_version` 테이블 수동 삭제 후 `migrate-db` 재실행 → legacy stamp 오판 위험
|
|
|
|
## Edge Cases
|
|
|
|
- **TimescaleDB 미설치 환경**: ensure_schema()의 TimescaleDB hypertable 생성은 `pg_extension`에서 timescaledb 존재 여부를 먼저 확인한다. 미설치 시 포트폴리오 운영은 가능하나 시계열 최적화 없이 작동한다.
|
|
- **ensure_schema()의 v1 스키마 미포함**: `broker_credentials`, `ledger_trades`, `runtime_id` 컬럼은 ensure_schema()에 없다. ensure_schema()만 실행한 환경에서 GET /v1/user_info 또는 POST .../ledger/trades를 호출하면 DB 오류가 발생한다. `migrate-db`로 해결한다.
|
|
- **비어있는 alembic_version**: `bootstrap_or_upgrade_to_head()`는 먼저 `alembic_version` 테이블 존재를 확인한 뒤, 없으면 legacy 테이블 존재를 확인한다. fresh DB이면서 alembic_version 없으면 full upgrade 경로를 탄다.
|
|
|
|
## Architecture Impact
|
|
|
|
`both` — docs/project-structure.md와 docs/runtime-flows.md 모두 갱신
|
|
|
|
## Reference Doc Update Plan
|
|
|
|
| 문서 | 추가/수정 내용 | REQ |
|
|
|------|----------------|-----|
|
|
| `README.md` | Local DB 검증 절차 섹션 추가 (준비, init-db, health-check, smoke flow, 반복 실행 기준) | REQ-001, REQ-003 |
|
|
| `docs/runtime-flows.md` | "Flow: Local DB Initialization and Validation" 추가 (init-db/migrate-db 흐름, Alembic/ensure_schema() 경계) | REQ-001, REQ-002, REQ-003 |
|
|
| `docs/project-structure.md` | "Schema Ownership Boundary" 섹션 추가 (객체별 소유 경로, 허용/금지 순서, 범위 제외 명시) | REQ-002, REQ-003 |
|