Files
llm-wiki/raw/branch-notes/feature-cachestore-multi-backend-router.md

14 KiB

title, source_type, status, branch, parent_branch, related_projects, tags, created, target_merge, status_label, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, contract_packet_sha256
title source_type status branch parent_branch related_projects tags created target_merge status_label id kind project work_item inherits refines overrides depends_on contract_packet contract_packet_sha256
branch / feature-cachestore-multi-backend-router branch-note raw feature-cachestore-multi-backend-router
ca-skeleton
branch
ca-skeleton
cache
decorator
fail-open
outbound-adapter
2026-06-12 in-progress BR-CA-SKELETON-OPERATIONAL-CONTRACT-049 project-work-item ca-skeleton-operational-contract WI-CA-SKELETON-OPERATIONAL-CONTRACT-049
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MODULE-LAYOUT-001@1
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-RESILIENCE-001@1
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-FRAMEWORK-001@1
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-TEST-001@1
WI-CA-SKELETON-OPERATIONAL-CONTRACT-024
1 42ff5787aebde944ffe6a393e9d9f1621e3c75fc5abb1ba1e85d64d6d7c71a77

branch: feature-cachestore-multi-backend-router

Layer: raw/branch-notes/ — 캐시 다중 백엔드 라우터 계획의 단계별 결정·진행 기록.

[!important] 주도권 이전 고지 (2026-07-28) 아래 관심사의 owner 가 신규 branch 로 이동했다. 근거·절차: docs/superpowers/specs/2026-07-28-ca-skeleton-production-capability-feature-decomposition-design.md §5.

이전 ID 대상 D-row 이전한 관심사 신규 owner
H4 D1·D2·D3 cache 실패 등급 — fail-open 경계 (codec 결함을 miss 로 위장하지 않음) raw/branch-notes/feature-cache-strategy-registry-contract

본문은 아직 제거하지 않았다. 신규 branch 는 현재 스캐폴딩 상태(D-row 없음)이므로, 지금 본문을 지우면 결정 근거가 소실된다. 위 D-row 는 신규 branch 의 /branch-spec 이 재판정을 마칠 때까지 잠정 근거로만 유효하며, 그 시점에 설계 §5.2 6단계에 따라 포인터(superseded → [[<신규 branch>]] D<m>)로 치환한다.

이 기간 중 새 결정을 본 branch 에 추가하지 않는다 — 신규 owner 에 작성한다.

부모 (필수)

형제 branch:

묶음

본 feature branch 는 leaf — 자식 자료 없음. 구현 진행 중에 errors / interview prep 이 누적되면 본 섹션에서 그룹화.

오류 기록 (본 feature 작업 중 발생)

  • (없음 — Task 2 clean)

면접 준비 (이 작업에서 나올 수 있는 면접 질문)

  • "fail-open 을 왜 별도 데코레이터로 분리했나?" — 정책 드리프트 방지: 새 백엔드가 try/catch 를 직접 구현하면 로그 포맷·로직이 달라질 위험.
  • "RedisCacheStore 가 이미 fail-open 이었는데 왜 분리가 필요한가?" — 다음 백엔드(Memcached 등)에 동일 정책을 재사용하기 위해. SRP 적용.

브랜치 계약 패킷

  • 생성 시 프로젝트 개정: 1
  • 패킷 스키마: contract_packet: 1
  • 완료 조건: cache backend 선택·fallback·failure routing과 contract test가 명시된다

상속한 프로젝트 결정

Decision Ref Project Summary Branch Application Source
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MODULE-LAYOUT-001@1 Gradle multi-module에서 domain-core·application-core·adapter-*·shared-contract·app-bootstrap·sample-portfolio 책임을 분리한다 Work Item 완료 조건에 적용 raw/project-notes/ca-skeleton-operational-contract
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-RESILIENCE-001@1 Resilience4j가 default이며 Spring Retry는 simple blocking retry에만 허용한다 Work Item 완료 조건에 적용 raw/project-notes/ca-skeleton-operational-contract
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-FRAMEWORK-001@1 framework는 Spring Boot 3.5.14다 Work Item 완료 조건에 적용 raw/project-notes/ca-skeleton-operational-contract
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-TEST-001@1 test framework는 JUnit 5다 Work Item 완료 조건에 적용 raw/project-notes/ca-skeleton-operational-contract

브랜치 지역 결정

기존 branch-local 결정은 아래 ## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.

Decision ID Decision Relation Supporting Claims Status

선언한 예외

Override ID Overrides Reason Approval Status

목표

RedisCacheStore 내부에 고착된 fail-open try/catch 정책을 데코레이터(FailOpenCacheStore)로 분리하여, 미래 캐시 백엔드들이 정책 드리프트 없이 동일한 fail-open 계약을 재사용하게 한다.

  • 이슈: (내부 계획 — docs/superpowers/plans/2026-06-12-cachestore-multi-backend-router.md)
  • PR: TBD

범위

포함 범위

  • Task 2: FailOpenCacheStore 데코레이터 신규 작성 (정책 공통화)
    • src/adapter-outbound/.../cache/FailOpenCacheStore.java
    • src/adapter-outbound/.../cache/FailOpenCacheStoreTest.java (4계약 TDD)
  • Task 5: CacheStoreRouter 논리명 라우팅 + 부팅 검증 + D4 fail-fast
    • src/adapter-outbound/.../cache/CacheStoreRouter.java
    • src/adapter-outbound/.../cache/CacheStoreRouterTest.java (5계약 TDD)
  • 향후 Task 3: RedisCacheStore 슬림화 (내부 try/catch 제거 → FailOpenCacheStore 위임)
  • 향후 Task N: 추가 백엔드 바인딩 (Memcached 등)

제외 범위

  • 이번 Task 에서 RedisCacheStore/RedisCacheAdapterConfig 수정 없음 (다음 Task 몫).
  • Spring @Configuration 등록 (다음 Task 몫 — Task 5 에서는 순수 Java 클래스만).

TODO

Task 2 완료. Task 5 완료. Task 3 이후는 별도 dispatch.

결정 사항 (decisions)

  • 2026-06-12: fail-open 정책(try/catch + logFailure → Optional.empty)을 FailOpenCacheStore 데코레이터로 추출. 모든 백엔드는 위임으로만 정책을 받는다.
  • 2026-06-12: CacheBackendException 은 다음 Task 에서 신규 작성. 현재 javadoc 은 {@code} 임시 링크.
  • 2026-06-12: 데코레이터는 final — 서브클래싱 차단으로 정책 드리프트 방지.
  • 2026-06-12 (Task 5): CacheStoreRouter 는 논리명(worklog) → 백엔드 ID(redis) 매핑만 담당. Spring 의존 없는 순수 Java. 생성자에서 바인딩-백엔드 정합 검증(startup validation). 미바인딩 접근은 AdapterDisabledException(D4 fail-fast). resolve()private — B7 ACL return type 규칙 준수 (CacheStore 타입 노출 없음).

결정-근거 매핑

각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시.

Decision ID Decision (요약) Supporting Claims Evidence Strength Open Risk
D1 fail-open 정책을 데코레이터로 분리 (Decorator pattern) 기존 RedisCacheStoreTest 4계약이 동일 logback ListAppender 패턴으로 검증됨 — 패턴 재사용 가능성 확인 internally-verified (ca-tmpl 기존 코드 관찰) CacheBackendException 미존재 — 다음 Task 에서 생성 전까지 javadoc 링크 불완전
D2 FailOpenCacheStoreCacheStore 구현 + final Decorator pattern — GoF 패턴 (UNSUPPORTED_DECISION — 외부 verbatim raw 없음) internal-policy 서브클래싱 차단이 확장성에 제약이 될 수 있음. 현재 단일 String 타입 캐시만 지원
D3 get 실패 = Optional.empty() 반환, put 실패 = silent swallow 기존 RedisCacheStore 의 fail-open 계약과 일치 — CacheStore 인터페이스 javadoc 에 명시됨 internally-verified 호출자가 cache-miss 를 source-of-truth fallback 으로 처리해야 함 — 호출 측 계약 별도 확인 필요

검증해야 할 주장

Claim Why uncertain How to verify Status
FailOpenCacheStore 가 future RedisCacheStore 슬림화 후에도 동일 4계약을 보장 현재 RedisCacheStore 는 수정 미완료 Task 3 완료 후 RedisCacheStoreTest 전체 통과 확인 planned
CacheBackendException 도입 후 javadoc {@link} 복원 시 컴파일 안전 다음 Task 에서 생성 예정 Task 3 에서 {@code}{@link} 교체 + 컴파일 확인 planned
FailOpenCacheStore 가 미래 백엔드(Memcached 등)에 실제로 재사용 가능 현재 String 키/값만 지원 — 타입 파라미터화 필요 여부 미검토 다음 백엔드 도입 Task 에서 확인 open

근거 (필수, 최소 1개+)

Source 정당화하는 결정
기존 RedisCacheStoreTest (in-repo) logback ListAppender 패턴 재사용 — D1
CacheStore 인터페이스 javadoc (in-repo) fail-open 계약 정의의 SSOT — D3
raw/branch-notes/feature-cache-consistency-contract Redis unavailable degrade 계약의 상위 결정 맥락

진행 현황

Task 상태 커밋
Task 1: AdapterDisabledException detail 오버로드 (shared-contract) ✓ 완료 (테스트 5 PASS) 미커밋 (사용자 git 금지 지시)
Task 2: FailOpenCacheStore 데코레이터 ✓ 완료 (파일 2개 신규, 테스트 4개 PASS) 미커밋 (사용자 git 금지 지시)
Task 3: RedisCacheStore 슬림화 + CacheBackendException ✓ 완료 (품질리뷰 FIX 포함, RedisCacheStoreTest 5 PASS) 미커밋
Task 4: CacheBindingSettings (app.cache.bindings.*) ✓ 완료 (테스트 2 PASS) 미커밋
Task 5: CacheStoreRouter 논리명 라우팅 + D4 fail-fast ✓ 완료 (파일 2개 신규, 테스트 5개 PASS) 미커밋 (사용자 git 금지 지시)
Task 6: 조립 전환 (sentinel 폐기, @Bean(name="redis") 기여, OCP 증명 테스트) ✓ 완료 (:adapter-outbound:test 137 PASS) 미커밋
Task 7: 문서 정합화 (CacheStore javadoc / adapter-outbound CLAUDE.md / application.yml 주석) ✓ 완료 (메인 에이전트 직접 수행 — 사용자 지시로 서브에이전트 체인 중단) 미커밋
Task 8: 전체 가드레일 검증 ✓ 완료 — verifyCleanArchitectureDependencies PASS, ArchUnit CleanArchitectureTest PASS, DisabledCacheStore 잔존 참조 0건, 전체 ./gradlew check BUILD SUCCESSFUL (32s) -
후속 리팩터: CacheBackend 마커 인터페이스 (빈이름 매직 제거) + fail-open 중앙화 ✓ 완료 (사용자 비평 수용, 메인 에이전트 직접) — 기여 계약을 "빈 이름 = backendId" 규약에서 CacheBackend.backendId() 타입 명시 계약으로 전환; FailOpenCacheStore 합성을 백엔드 Config 관례에서 CacheRouterConfig 중앙 적용으로 이동(구조적 보장); 라우터에 중복 backendId 부팅 검증 추가; RedisCacheAdapterConfig는 raw RedisCacheStore 기여만 하는 얇은 Config로 축소. 캐시 범위 29 tests PASS, ArchUnit PASS(B7: backendId()는 String 반환이라 합법), 의존 매트릭스 PASS. (actually-implemented, locally-verified) 미커밋
  • 기록 분산 주의: Task 1·3·4·6 의 상세 구현 기록은 세션이 돌던 git 브랜치명 기준으로 raw/branch-notes/feature-domain-event-outbox-contract 의 "진행 중 메모"에 적재됨 (2026-06-12 항목들). 본 노트가 이 feature 의 SSOT 이며, 해당 항목들은 이 작업의 기록이다.

진행 중 메모

  • provider 선택·fallback·실패 routing의 세부 상태는 위 진행 현황과 TODO를 기준으로 추적한다.

구현 가이드

  • core는 CacheStore SPI만 알고 provider registry가 설정값을 실제 adapter로 해석한다.
  • 지원하지 않는 provider·중복 key·필수 backend 부재는 startup에서 실패시키고 runtime silent fallback을 만들지 않는다.
  • backend별 동일 contract suite로 get·put·evict·timeout 의미를 대조한다.

엣지·실패·의존

  • provider 이름 오타나 중복 등록은 잘못된 backend 선택으로 이어지므로 fail-fast가 필요하다.
  • cache consistency 계약과 runtime configuration 계약에 의존한다.

마주친 문제

  • backend별 capability 차이를 공통 SPI에 과도하게 노출하면 core가 특정 기술에 결합된다. 공통 최소 계약 밖 기능은 adapter-local로 둔다.

관련 일일 노트

  • 별도 일일 노트 없음.

완료 후 정리

  • PR 링크: (미정 — 사용자가 일괄 커밋 예정, 커밋·푸시 전)
  • 리뷰 메모: Task 별 ca-architect-sentinel → ca-spec-reviewer → ca-quality-reviewer 체인 수행 (Task 1-6). spec NEEDS_FIX 2건은 모두 선재 working-tree 변경(outbox 작업·세션 이전 javadoc 줄바꿈)으로 판명되어 controller Override. 품질 Important 2건(Task 3 테스트 갭)은 수정 완료. Task 7-8 은 사용자 지시로 메인 에이전트 직접 수행 (소규모 작업에 체인 과잉).
  • 머지 결과 / 배포 환경: 미머지 (작업 트리 상태)
  • wiki 추출 대상 (verified만, wiki/projects/로만 추출):
    • actually-implemented: FailOpenCacheStore 데코레이터 패턴 + 4계약 TDD
    • actually-implemented: CacheStoreRouter 논리명 라우팅 — startup-time binding validation + D4 fail-fast + B7 ACL 준수 — 5계약 TDD
    • actually-implemented: CacheBackend 타입 명시 기여 모델 — ObjectProvider<List<CacheBackend>> 수집으로 OCP 달성 (2번째 백엔드 = 신규 Config 파일만; OptionalAdapterBeanGatingTest.a_second_backend_plugs_in_... 이 증명 테스트). 초기 구현은 빈이름=backendId 규약이었으나 사용자 비평(빈이름 매직·무차별 수집·탐색 불가) 수용 후 마커 인터페이스로 교체 — backendId() 가 String 반환이라 B7 합법이라는 발견이 전환점
    • actually-implemented: B7 ArchUnit 제약이 설계를 두 번 바꾼 사례 — (1) 바인딩 record(accessor 가 CacheStore 반환) 폐기 → 빈이름-키 맵 주입, (2) String 반환 메서드는 합법임을 재발견 → CacheBackend 마커 인터페이스로 최종 수렴
  • 추출하지 않을 항목 (planned / documented-only / abandoned):
    • application-core 소비자 포트 (planned — 소비 계층 결정 대기), put TTL 옵션 객체 (planned), L1/L2 컴포지트 (planned)