Files
clean-architecture-frontend…/docs/architecture/decisions/VD-10-optional-capability-recipes.md
T

6.0 KiB

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가 통과해야 한다.

선택과 설치 절차

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으로 묶지 않는다.