feat: add TechAPI data layer - #16
Merged
Merged
Conversation
v1에는 도메인 데이터 계층이 없었다. 제품 정보가 화면 코드 안에 Map 리터럴로 박혀 있었고 모델 클래스도 리포지토리도 없었다. 위젯을 짓기 전에 데이터 계층부터 세운다. 소스 추상화 - TechApiSource: DumpSource(GitHub Pages 정적 덤프) / RestSource(api.techapi.dev) - 덤프는 실제 엔드포인트를 인프로세스로 replay해 생성되므로 REST와 응답 스키마가 같다. 차이는 URL 조립뿐이라 그 지점만 흡수한다 - api.techapi.dev는 아직 미배포라 DumpSource가 기본값 DTO (freezed + json_serializable) - Smartphone, Cpu, Gpu, Soc, Brand, CollectionPage - score는 컬렉션마다 축이 다르다. 공통 ScoreMetric을 두고 SmartphoneScore(5축) / CpuScore(single·multi) / GpuScore(graphics) / SocScore(cpu·system)로 나눴다 - 데이터셋 특성상 거의 모든 필드가 nullable이다. score 객체가 있어도 개별 축이 null인 레코드가 흔하다 - build.yaml에서 field_rename: snake 전역 적용 오류 처리 - Failure를 NetworkFailure / NotFoundFailure / ParseFailure / ServerFailure로 분류 - 리포지토리는 예외를 던지지 않고 Result<T>를 반환한다. DTO 파싱 중 터지는 TypeError도 ParseFailure로 감싸 밖으로 새지 않게 한다 테스트 25건 - 픽스처는 손으로 만든 가짜가 아니라 실제 덤프에서 내려받은 응답이다. 스키마가 바뀌면 테스트가 깨지고 그게 의도다 - 완료 기준 충족: 위젯 없이 테스트만으로 galaxy-s25의 score 5축과 overall을 뽑아낸다 - tool/smoke_techapi.dart로 실제 원격 왕복도 확인 Closes #4
CI의 코드 생성 최신성 검사가 잡아냈다. 재생성 결과가 커밋된 것과 4,873줄 삽입 / 9,005줄 삭제만큼 달랐다. 원인은 생성 시점과 pubspec 수정 순서. build_runner를 돌릴 때 environment.sdk 가 아직 ^3.5.0 이었고, json_serializable 이 요구하는 ^3.8.0 으로 올린 건 그 뒤였다. freezed 와 json_serializable 은 패키지의 language version 에 따라 다른 코드를 뽑기 때문에 생성물이 낡은 채로 남았다. 생성 로그에 "language version (3.5.0) does not match required range ^3.8.0" 경고가 떠 있었는데 SDK 제약만 올리고 재생성하지 않았다. 재생성 후 테스트 25건 그대로 통과.
This was referenced Aug 7, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #4
배경
v1에는 도메인 데이터 계층이 없었다. 제품 4종이
Phone.dart안에Map리터럴로 박혀 있었고 모델 클래스도 리포지토리도 없었다. 위젯을 짓기 전에 데이터 계층부터 세운다.소스 추상화
api.techapi.dev가 아직 미배포(DNS 미해결)라DumpSource(GitHub Pages 정적 덤프)가 기본값이다. 덤프는 TechEngine의app/dump.py가 실제 FastAPI 엔드포인트를 인프로세스로 replay해 생성하므로 REST와 응답 스키마가 같다. 차이는 URL 조립 하나뿐이고, 테스트로 그 사실을 고정해뒀다.DTO
SmartphoneCpuGpuSocBrandCollectionPage.score 스키마가 컬렉션마다 다르다. 공통
ScoreMetric{index, percentile, tier, era, source}을 두고 나눴다.perfsingle,multigraphicscpu,systembuild.yaml에서field_rename: snake를 전역 적용해base_model_slug→baseModelSlug매핑을 자동화했다.데이터셋 특성 — 거의 모든 필드가 nullable
이건 방어적 설계가 아니라 실제 응답이 그렇다.
energy-200레코드는 score 객체가 있는데도 개별 축이 null이다.SoC의
manufacturer는id조차 없이 온다. 그래서Brand.id도 nullable이다.오류 처리
Failure를NetworkFailure/NotFoundFailure/ParseFailure/ServerFailure로 나눴다. UI가 원인에 따라 다르게 반응해야 하기 때문이다 — 네트워크 문제는 재시도 버튼을, 없는 레코드는 빈 상태를 보여야 한다.리포지토리는 예외를 던지지 않고
Result<T>를 반환한다. DTO 파싱 중 터지는TypeError도ParseFailure로 감싸 밖으로 새지 않는다.검증
테스트 25건 전부 통과. 픽스처는 손으로 만든 가짜가 아니라 실제 덤프에서 내려받은 응답이다. 스키마가 바뀌면 테스트가 깨지고, 그게 의도다.
완료 기준 충족 — 위젯 없이 테스트만으로
galaxy-s25의 5축과 종합을 뽑아낸다.tool/smoke_techapi.dart로 실제 원격 왕복도 확인했다.flutter analyze lib/core lib/data lib/domain test tool→ No issues found.부수 변경
environment.sdk를^3.5.0→^3.8.0. json_serializable 6.14가 요구한다freezed는 pub이 처음 프리릴리스(4.0.0-dev.3)를 골랐다. 데이터 계층 기반에 dev 버전은 부적절하다고 판단해 안정 버전^3.0.0(해석 결과 3.2.5)으로 내렸다후속 (이 PR 범위 밖)
스마트폰 목록 인덱스 크기를 계측했다 — 19 MB / 93,396건. 앱 시작 시 통째로 받는 건 불가능하다. #8에 기록해뒀다.