114 lines
6.0 KiB
Markdown
114 lines
6.0 KiB
Markdown
# VD-10: 선택형 frontend capability recipe
|
|
|
|
- 상태: Accepted
|
|
- 결정일: 2026-07-26
|
|
- 적용 브랜치: `feature-frontend-optional-adapter-recipes`
|
|
- 현재 선택 capability: 없음
|
|
- 재검토: 실제 프로젝트가 realtime, offline, PWA, file, generated API,
|
|
feature flag, worker, multi-tab, browser permission, client workflow,
|
|
large-data UI 또는 production analytics/error provider를 요구할 때
|
|
|
|
## 배경
|
|
|
|
서버의 PostgreSQL, MongoDB, Redis, Kafka, MinIO 같은 기술을 브라우저가 직접
|
|
소비하지는 않는다. 프론트의 변화 지점은 권한 있는 HTTP/BFF, push event,
|
|
offline persistence, file protocol, browser runtime, 사용자 동의와 UI 성능
|
|
경계다. 이 capability를 “언젠가 필요할 수 있다”는 이유로 모두 설치하면 초기
|
|
bundle, 공급망, runtime config, 보안 표면과 업데이트 비용만 늘어난다.
|
|
|
|
반대로 문서에 이름만 적으면 실제 프로젝트에서 port 위치, cancellation,
|
|
fallback, fake와 제거 기준을 다시 설계해야 한다. 따라서 production runtime에
|
|
아무것도 설치하지 않되 검증 가능한 vendor-neutral recipe를 저장소 밖이 아닌
|
|
별도 opt-in 경계에 유지한다.
|
|
|
|
## 결정
|
|
|
|
1. `config/recipes/frontend-capability-recipes.json`이 12개 recipe의 선택 기준,
|
|
금지 조건, port/fake, failure matrix, lifecycle cleanup, owner,
|
|
security/privacy, gzip budget, fallback, server-state 정책과 제거 절차의
|
|
machine-readable SSOT다.
|
|
2. 현재 실제 소비 요구와 project owner가 없으므로 12개 상태는 모두
|
|
`RECIPE_AVAILABLE`이며 `INSTALLED`가 아니다. production runtime dependency와
|
|
composition registration은 0개다.
|
|
3. `recipes/frontend-capabilities`의 TypeScript port와 fake/unavailable adapter는
|
|
실행 가능한 설계 예시다. `src` 또는 production entry가 이 디렉터리를 import할
|
|
수 없다.
|
|
4. 프로젝트가 capability를 선택하면 필요한 최소 contract를
|
|
application-owned output port 또는 presentation facade로 이동하고, concrete
|
|
vendor adapter는 local adapter 경계에 둔다. recipe 디렉터리를 production에서
|
|
그대로 import하지 않는다.
|
|
5. WebSocket/SSE처럼 연결은 outbound이고 수신 event는 inbound인 양방향 기술도
|
|
한 종류의 “adapter”로 뭉개지 않는다. 연결·credential·reconnect 정책과
|
|
event validation·input invocation을 분리한다.
|
|
6. Zustand/Redux Toolkit/state machine은 실제 cross-page client-only workflow가
|
|
확인된 경우 하나만 선택한다. URL, component state, Context, TanStack Query가
|
|
이미 소유한 상태를 복제하지 않는다.
|
|
7. browser credential은 localStorage, URL, recipe store, telemetry 또는
|
|
BroadcastChannel에 넣지 않는다. 브라우저가 database/object store에 직접
|
|
접속하는 recipe도 금지한다.
|
|
8. lifecycle이 있는 capability는 unsubscribe, close, unregister, dispose,
|
|
cancel 또는 `AbortSignal`을 계약과 contract test에 포함해야 한다.
|
|
9. 선택하지 않은 recipe sentinel이나 reference runtime source, vendor
|
|
dependency가 production bundle에 들어가면 gate를 실패시킨다.
|
|
`referenceRuntime`이 있는 recipe는 catalog `sourceRoots` 전체를 별도의
|
|
production-mode synthetic entry로 deterministic하게 bundle/minify하되
|
|
tree-shaking을 끄고, 모든 출력의 gzip 합계가 recipe budget을 넘으면
|
|
production composition 여부와 무관하게 실패시킨다. 2026-07-28 최초 실측에서
|
|
`offline-indexeddb`가 32,930 bytes였으므로 측정 없이 선언됐던 8,000 bytes를
|
|
약 9% headroom의 36,000 bytes로 교정했으며 다른 budget은 자동 인상하지
|
|
않는다.
|
|
10. recipe 전체를 제거한 임시 worktree에서 base typecheck, architecture,
|
|
unit/component/integration test와 production build가 통과해야 한다.
|
|
|
|
## 선택과 설치 절차
|
|
|
|
```text
|
|
measured product/runtime need
|
|
-> project owner + security/privacy classification
|
|
-> recipe trigger/forbidden/fallback review
|
|
-> VD-10 amendment with one selected capability
|
|
-> application port or presentation facade copied into src
|
|
-> one concrete adapter under local adapter boundary
|
|
-> composition-only wiring
|
|
-> contract/failure/cleanup/integration tests
|
|
-> bundle + dependency baseline approval
|
|
-> INSTALLED only after all evidence passes
|
|
```
|
|
|
|
도입 커밋에는 owner, 선택 이유, 대안, gzip 차이, runtime config, browser support,
|
|
failure UX, observability, rollback과 제거 명령을 기록한다. vendor가 필요한
|
|
behavior를 fake만으로 확인하고 `INSTALLED`로 바꾸지 않는다.
|
|
|
|
## 증적
|
|
|
|
- catalog: `config/recipes/frontend-capability-recipes.json`
|
|
- contracts/fakes: `recipes/frontend-capabilities`
|
|
- 상세 runbook: `docs/architecture/optional-adapter-recipes.md`
|
|
- file/IndexedDB/OPFS/Cache 심층 결정:
|
|
`docs/architecture/decisions/VD-11-browser-file-and-origin-storage.md`
|
|
- browser data 상세 설계:
|
|
`docs/architecture/browser-file-and-origin-storage.md`
|
|
- realtime/Web Push/Polling 심층 설계와 결정:
|
|
`docs/architecture/realtime-events-web-push-and-bounded-polling.md`,
|
|
`docs/architecture/decisions/VD-28-realtime-events-web-push-and-bounded-polling.md`
|
|
- contract test: `tests/recipes/optional-capability-contracts.test.ts`
|
|
- negative fixture:
|
|
`tests/fixtures/optional-recipes/forbidden`
|
|
- validation:
|
|
`scripts/check-optional-recipes.ts`
|
|
- removal:
|
|
`scripts/test-optional-recipe-removal.ts`
|
|
- evidence:
|
|
`artifacts/quality/optional-recipes.json`,
|
|
`artifacts/quality/optional-recipe-fixtures.json`,
|
|
`artifacts/tests/optional-recipes.xml`,
|
|
`artifacts/tests/optional-recipe-removal.xml`
|
|
|
|
## Rollback
|
|
|
|
현재 branch는 runtime dependency나 production composition을 바꾸지 않으므로
|
|
recipe catalog, example과 gate를 함께 revert하면 RP-11 상태로 돌아간다. 실제
|
|
프로젝트에서 선택한 capability는 그 capability의 port/adapter/composition/
|
|
dependency commit만 revert한다. 여러 vendor 도입을 하나의 되돌릴 수 없는
|
|
commit으로 묶지 않는다.
|