# 함수 설계서: `SeedImporter.importIfNeeded` (#204) > 부모 설계서: [README.md](./README.md) · 상태: Draft > 작성: [AI] Architect · 구현: `lib/data/seed/seed_importer.dart::SeedImporter.importIfNeeded` (TBD) > 테스트: `test/data/seed/seed_importer_test.dart` (TBD) ## 1. 시그니처 ```dart class SeedImporter { SeedImporter(this._db, {this._assetLoader = const RootBundleLoader()}); final AppDatabase _db; final AssetLoader _assetLoader; Future importIfNeeded(); // 멱등 } abstract class AssetLoader { Future loadString(String path); } ``` ## 2. 책임 앱 첫 실행 시 `assets/seed/*.json` 7 파일을 읽어 카탈로그 테이블 7 개 + `users('u_local_default')` 1 행을 batch insert. 두 번째 실행부터는 noop (멱등성). ## 3. 입력 | 파라미터 | 타입 | 제약/검증 | 설명 | |----------|------|-----------|------| | `_db` | `AppDatabase` | not null | open 된 Drift DB | | `_assetLoader` | `AssetLoader` | testable | prod = `RootBundleLoader`, test = fixture loader | ## 4. 출력 - **반환**: `Future`. 성공/실패는 예외로 전달. - **부수효과**: - 카탈로그 7 테이블 insert (~150~180 rows 총합). - `users` 1 row insert. - `meta_kv['seeded_v1'] = 'true'` set. ## 5. 동작 / 알고리즘 ``` function importIfNeeded(): 1. existing = db.metaKvDao.find('seeded_v1') 2. if existing?.value == 'true': return # 멱등 skip 3. plan = [ ('references.json', References, _adaptReference), ('protocols.json', Protocols, _adaptProtocol), ('break_protocols.json', BreakProtocols, _adaptBreakProtocol), ('common_frames.json', CommonFrames, _adaptCommonFrame), ('methodologies.json', Methodologies, _adaptMethodology), ('frame_patterns.json', FramePatterns, _adaptFramePattern), ('reward_menu_items.json', RewardMenuItems, _adaptRewardMenuItem), ] 4. await db.transaction(() async: for (assetName, table, adapter) in plan: raw = await _assetLoader.loadString('assets/seed/' + assetName) items = jsonDecode(raw) as List> companions = items.map(adapter).toList() await db.batch((b) => b.insertAll(table, companions, mode: InsertMode.insertOrReplace)) await _ensureLocalDefaultUser() await db.metaKvDao.put('seeded_v1', 'true') 5. ) # transaction commit ``` ### 5.1 adapter 책임 각 `_adapt*` 함수는 `Map` (JSON) → Drift `Companion`. - nested object (예: `protocol.default_anchor`) → JSON string column. - array (예: `procedure[]`) → JSON string column. - enum string → 그대로 (CHECK 가 검증). - ULID validation 은 본 import 에선 skip (시드는 의미 식별자 사용, ULID 형식 X). ### 5.2 user 보장 ```dart Future _ensureLocalDefaultUser() async { await _db.into(_db.users).insertOnConflictUpdate( UsersCompanion.insert( id: 'u_local_default', createdAt: DateTime.now().toIso8601String(), locale: const Value('ko-KR'), timezone: const Value('Asia/Seoul'), ), ); } ``` ## 6. 에러 & 실패 모드 | 조건 | 처리 | 결과 | |------|------|------| | asset 파일 누락 | rootBundle 예외 → 트랜잭션 rollback | `seeded_v1` flag 미설정. 다음 부팅에 재시도 | | JSON parse 실패 | `FormatException` → rollback | 동일 | | CHECK 제약 위반 (시드 데이터 오류) | sqlite 예외 → rollback | dev 가 시드 JSON 수정 + 재빌드 | | FK 무결성 위반 (예: protocol.reference_ids 가 references 에 없음) | 본 Phase 는 reference_ids 가 JSON 컬럼이라 FK 강제 없음. 단 catalog_dao 가 load 시 broken link 발견 시 warning log | warning only | | 트랜잭션 중간 device crash | 다음 부팅 시 flag false 상태 → 재시도. sqlite WAL 로 일관성 유지 | OK | ## 7. 엣지케이스 - 두 번째 호출 (이미 seeded) → metaKv read 1 회 + return. < 5 ms. - 다른 instance 가 동시 호출 (race): Drift 의 transaction 직렬화로 OK. 두 instance 중 하나가 먼저 seeded_v1=true 박으면 다른 instance 가 짧은 시간 안에 read → skip. - 시드 JSON 의 ID 중복: `InsertMode.insertOrReplace` 가 마지막 값 우선. dev 가 시드 정리 필요. - 빈 시드 파일 (`[]`): 해당 테이블 0 rows, flag 는 set. AC-2 의 "≥ 0 행" 통과. ## 8. 복잡도 / 성능 - 시간: 1 회 ~ 1 초 (JSON parse + 7 batch insert + 1 commit). 첫 부팅 1 회만. - 멱등 호출: < 5 ms. - 메모리: 시드 JSON 전체 RAM 적재 (~수백 KB). OK. ## 9. 의존성 - `AppDatabase` + 모든 카탈로그 테이블 + `meta_kv`. - `rootBundle` (Flutter asset loader). - `dart:convert.jsonDecode`. - adapter 함수들 (`_adaptProtocol`, etc.). ## 10. 테스트 케이스 - [ ] 정상 first-run: fixture 7 파일 → 모든 카탈로그 시드 + users 1 row + flag set - [ ] 멱등 second-run: 이미 seeded → noop, no DB writes - [ ] 파일 누락 simulate: protocols.json 없음 → 예외 + 전체 rollback + flag 미설정 - [ ] JSON malformed: protocols.json = "not json" → FormatException + rollback - [ ] CHECK 위반: protocol.category='invalid' → sqlite 예외 + rollback - [ ] 빈 배열: protocols.json = "[]" → 0 rows + flag set + 멱등 두 번째 호출 OK - [ ] users insertOnConflictUpdate: 이미 같은 id 있어도 conflict 없음 - [ ] flag 강제 reset 후 호출 → 재import (시드 변경 시뮬레이션) ## 11. 추적성 - 인수조건: #204 AC-2, AC-3, AC-15. - 관련 설계서: [05-seed-data.md](./05-seed-data.md), [04-migrations.md](./04-migrations.md). - 시드 SoT: 4 마크다운 (`huberman-protocols.md`, `habit-todo-methodologies.md`, `habit-breaking-protocols.md`, `nutrition/diet-protocols.md`).