Phase 1 설계서 작성 완료. docs/design/204-flutter-bootstrap/ 13 개 파일: - README.md (12 섹션 모두 채움, 함수 19 개 명세, AC 16 항) - 01-project-structure.md (feature-first + layer-first 하이브리드) - 02-drift-schema-catalog.md (Catalog 7 테이블 Dart 정의) - 03-drift-schema-user.md (User 11 테이블 + R1~R10 강제 매트릭스) - 04-migrations.md (schemaVersion v1 + 인덱스 17 개) - 05-seed-data.md (assets/seed/*.json + first-run import) - 06-ux-contracts.md (체크인 R8 ≤ 60 초 흐름) - fn-recommend-variant / fn-compute-streak / fn-weekly-minimum-ratio - fn-validate-frame-level / fn-active-habit-quota / fn-seed-importer 핵심 결정: dose_variants 는 별도 habit_dose_variant 테이블로 정규화 (FK 무결성 + recommendVariant SQL 단순성). ADR-0002 승격 권장. Refs #204 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5.7 KiB
5.7 KiB
함수 설계서: SeedImporter.importIfNeeded (#204)
부모 설계서: README.md · 상태: Draft 작성: [AI] Architect · 구현:
lib/data/seed/seed_importer.dart::SeedImporter.importIfNeeded(TBD) 테스트:test/data/seed/seed_importer_test.dart(TBD)
1. 시그니처
class SeedImporter {
SeedImporter(this._db, {this._assetLoader = const RootBundleLoader()});
final AppDatabase _db;
final AssetLoader _assetLoader;
Future<void> importIfNeeded(); // 멱등
}
abstract class AssetLoader {
Future<String> 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<void>. 성공/실패는 예외로 전달. - 부수효과:
- 카탈로그 7 테이블 insert (
150180 rows 총합). users1 row insert.meta_kv['seeded_v1'] = 'true'set.
- 카탈로그 7 테이블 insert (
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<Map<String,dynamic>>
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<String,dynamic> (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 보장
Future<void> _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, 04-migrations.md.
- 시드 SoT: 4 마크다운 (
huberman-protocols.md,habit-todo-methodologies.md,habit-breaking-protocols.md,nutrition/diet-protocols.md).