- docs/design/226-catalog-gallery/README.md — 12 changed files, DisplayCategory enum, sealed CatalogItem, 5 scenarios - docs/design/226-catalog-gallery/fn-catalog_repository.md — 3-source unification algorithm + helpers - docs/design/226-catalog-gallery/fn-migration_v1_to_v2.md — first schema migration (DROP + reseed pattern) - docs/adr/0004-catalog-recategorization-and-first-migration.md — Catalog 재분류 + 첫 마이그레이션 정책 Refs #226
6.0 KiB
6.0 KiB
ADR-0004: Catalog 재분류 (8 displayCategory) + 첫 schema 마이그레이션 정책
상태: Accepted 날짜: 2026-06-12 · 결정자: [AI] Architect · 관련 이슈: #226
맥락 (Context)
Phase 2-A (#218) 종료 시점에 다음 두 문제가 동시에 드러났다.
- 선택 마비. 빈
HabitListScreen+ 자유 입력 단일 경로만 본 첫 진입 사용자가 Tiny Habits 의 "어떤 작은 행동부터?" 에 멈췄다. 시드 카탈로그 (protocols 34 + frame 30 + reward 30 + break 8 + diet 5 = 107) 가 APK 에 포함되나 UI 노출 0. - 카테고리 미스매치. 기존
Protocols.categoryCHECK 제약은health|meditation|motivation|habit|learning|diet6 값으로, Planner 가 정의한 8 displayCategory (빛/일주기·수면·운동·영양·집중·회복·감정·없애기) 와 직접 매핑되지 않는다. seed JSON 에 새 분류를 박아넣으면 CHECK 위배.
또한 본 이슈는 앱 출시 이후 첫 schema 변경 이다 — Phase 1 (#204) 의 schemaVersion = 1 이 onUpgrade: assert(false, 'Phase 1 has no upgrade path') 로 마감되어 있어, 마이그레이션 정책의 첫 사례를 세워야 한다.
결정 (Decision)
다음 3개 결정을 함께 채택한다.
DisplayCategoryenum 도입 (UI/조회 모델 레이어). 값 8개:lightCircadian, sleep, movement, nutrition, focusCognition, recoveryStress, emotionRelationship, breakHabit. Protocol 의 sourcecategory와는 분리된 별도 개념.Protocols.categoryCHECK 를 7 값으로 갱신 (breakHabit 제외 — Break 는 별도 테이블이라 매핑 1:1 자동). schema v1 → v2 마이그레이션으로 적용.- 첫 마이그레이션 전략 = "DROP + reseed" — read-only catalog 테이블에 한해
m.deleteTable(protocols)→m.createTable(protocols)(v2 CHECK) →kSeededV1Flag클리어 → 다음 부팅이SeedImporter재실행. user 테이블 (Habits, Phases, TrackerEntries 등) 무변화.
근거 (Rationale)
- enum 분리 (1) 가 source-of-truth 명확화. Protocol DB 의
category는 ETL 단계의 분류축이고, UI 의displayCategory는 사용자 멘탈 모델 축이다. 두 개념을 한 컬럼에 욱여넣으면 BreakProtocol (이미 자체category보유) 과 DietPattern (category없음) 통합 시 깨진다. - schema 마이그레이션 (2) 이 가상 매핑 (in-code dict) 대비 명시적. CHECK 위배가 컴파일/런타임 즉시 노출되므로 시드 작성 실수를 더 일찍 잡는다. 또한 향후 신규 Protocol 추가 시 정합성 자동 보장.
- DROP + reseed (3) 가 read-only 카탈로그에 대해 안전한 최단 경로. user 데이터를 건드리지 않아 위험도 낮음. ALTER TABLE 로 CHECK 만 갱신하는 방식은 SQLite 에서 직접 지원하지 않아 어차피 임시 테이블 + 복사 + rename 패턴이 필요한데, read-only catalog 라면 reseed 가 더 단순.
트레이드오프
- ✅ user 데이터 0 영향이라 사용자 위험 없음.
- ✅ 향후 catalog 갱신 (#FF1+) 시 동일 패턴 재사용 가능 — 마이그레이션 N→N+1 = "DROP catalog 테이블 + flag 클리어" 라는 일관 규칙.
- ❌ 시드 파일이 손상되면 부팅 실패 (CHECK 위배 throw). 그러나 본 함수는 unit test 가 명시적 검증.
- ❌
BreakProtocol의 카테고리는displayCategory.breakHabit단일 매핑 → 사용자가 "8 카테고리" 라고 들었지만 그 중 1개는 source 1개에서만 채워짐. 의도된 단순화 (Planner AC-4 가 8개 명시).
결과 (Consequences)
- 긍정:
- UI 코드가 source 구분 없이
displayCategory만으로 필터링/그룹핑 가능 → 갤러리/카드 구현 단순. - schema 진화 패턴 (v1→v2) 의 첫 reference 코드가 repo 에 등재. v3+ 시점에 재활용.
- 시드 갱신 워크플로 = "JSON 수정 → version bump → reseed" 한 줄 정리.
- UI 코드가 source 구분 없이
- 부정 / 비용:
- 사용자가 v1 DB 를 가진 채 앱 업데이트 시 첫 부팅에서
protocols테이블이 DROP 됨 (user 테이블은 무사). dev 단말 한정 — 베타 외부 배포 전이라 영향 0. schemaVersion2 도입으로MigrationStrategy.onUpgrade의 dispatch 로직 진입.if (from == 1 && to >= 2)분기 + 알 수 없는 경로 assert. 향후 v3 도입 시 같은 dispatch 에 한 줄 추가.
- 사용자가 v1 DB 를 가진 채 앱 업데이트 시 첫 부팅에서
- 후속 작업:
migration_v1_to_v2_test.dart가 user 테이블 무변화 + CHECK 갱신 + flag 클리어를 명시 검증 (3 케이스 필수).- 향후
DisplayCategory값 추가 시 본 ADR 갱신 (값 enum 확장은 Accepted 유지, 의미 변경 시 Superseded).
검토한 대안 (Alternatives Considered)
-
A. 가상 매핑 (in-code dict, schema 무변화) — Protocol.category 6 값을 그대로 두고,
DisplayCategory.fromProtocolCategory()가 코드 내 dict 로 8 값에 매핑.- 기각 사유: CHECK 가 6 값 그대로라 seed JSON 의 새 분류를 못 받음. Planner AC-4 의 "8 카테고리 재분류" 를 문자 그대로 충족하지 못함. 신규 Protocol 추가 시 CHECK 위배가 runtime 까지 안 잡힘.
-
B.
display_category신규 컬럼 추가 + 기존category유지 — protocols 에 새 컬럼 ADD COLUMN 으로 8 값 저장, 기존 6 값은 유산 컬럼.- 기각 사유: 컬럼 2개의 의미가 겹쳐 SoT 분기. ETL 단계에서 매번 두 컬럼 채워야 함. 6 값 컬럼이 영원히 dead weight.
-
C. catalog 테이블 전체를 in-memory 로 전환 (JSON 직독) — DB 에서 catalog 제거, 부팅 시 JSON 만 메모리 로드.
- 기각 사유: Phase 1 (#204) 의 ADR-0002 결정 정규화 = "어떻게" 를 뒤집는 큰 변경. read-only 라도 reference / habit 와의 FK 연결 손실. 본 이슈 scope 초과.
-
D. 마이그레이션 우회 (사용자 앱 재설치 안내) — 첫 마이그레이션 회피용으로 사용자에게 재설치 권장.
- 기각 사유: dev 단말 한정 단계라 기술적으로 가능하나, 첫 마이그레이션 reference 코드를 미루는 것 자체가 부채. 어차피 v3+ 에서 같은 결정 다시 해야 함 — 일찍 답하는 게 싸다.