Files
llm-wiki/raw/interviews/optional-adapter-3-layer-disabled-detection-2026-06-09.md

4.0 KiB

title, source_type, status, related_branches, related_projects, tags, created, status_label
title source_type status related_branches related_projects tags created status_label
interview / optional-adapter-3-layer-disabled-detection-2026-06-09 interview-prep raw
feature-integration-adapter-templates
ca-skeleton
interview
ca-skeleton
spring
clean-architecture
adapter
observability
2026-06-09 raw

interview: optional-adapter-3-layer-disabled-detection-2026-06-09

Layer: raw/interviews/ — 이 작업에서 정직하게 뽑을 수 있는 면접 질문/답변.

Parent / 부모

Q1. 선택형 어댑터(Kafka/Redis/Slack/Email)를 "꺼져 있음"으로 안전하게 보장하려면?

3계층으로 검출한다.

  • Layer 1 (startup, runtime) — Spring @ConditionalOnProperty(name="app.<domain>.<adapter>.enabled", havingValue="true", matchIfMissing=false). flag 미설정/false 면 real adapter bean 미등록(disabled bean count = 0). matchIfMissing=false 를 명시해 누락=disabled 가 사고가 아니라 의도가 되게 한다.
  • Layer 2 (build, static) — ArchUnit. (a) application layer 가 optional adapter 패키지를 import 하지 못하게 격리, (b) optional adapter 패키지의 모든 @Bean@ConditionalOnProperty 로 gating 됐는지 검사. 정적 검사는 "후보 클래스가 annotation 을 가짐" 까지만 보장한다.
  • Layer 3 (runtime, fail-fast) — disabled 일 때 port 를 만족시키는 sentinel(DisabledMessagePublisher 등)을 등록해, 우회 호출이 들어오면 AdapterDisabledException 으로 즉시 throw(silent no-op/timeout 대기 금지).

Q2. 각 계층의 한계는?

  • Layer 1 의 "bean count = 0 검증" 은 Spring 공식 검증 패턴이 아니라 프로젝트 자체 선택(통합 테스트로 assert).
  • Layer 2 는 runtime config 평가를 못 하므로 "실제 active 여부" 는 보장 못 함 → Layer 3 로 위임.
  • Layer 1 이 정상 경로에선 bean 자체를 안 만들어 호출 불가이므로, Layer 3 는 "Layer 1·2 를 우회한 호출의 최후 방어선" 일 뿐 정상 경로 코드가 아니다.

Q3. 어댑터별 실패를 fail-open 으로 둔 이유와 예외는?

  • skeleton 기본은 fail-open: 알림/캐시/메시지는 핵심 use case 의 부수효과라 전송/캐시 실패가 HTTP 5xx 로 승격되면 안 됨.
    • Kafka: publish 실패 → correlationId 부착 로그 + outbox/retry 위임, core 는 성공.
    • Redis: unavailable → cache-miss 로 graceful degrade(절대 INTERNAL 로 뭉개지 않음).
    • Slack/Email: 전송 실패 → 관측(metric/log)만, 단 provider body/PII 는 로그에 절대 미등장(로거 시그니처에 payload 인자 자체를 없애 구조적으로 차단).
  • 예외: notification 이 use case 의 primary outcome(예: 비밀번호 재설정 메일 자체가 목적)이면 도메인 branch 가 동기 + fail-closed 로 호출 — skeleton scope 밖.

Q4. disabled adapter runtime 호출에 startup 의 REQUIRED_ADAPTER_DISABLED 코드를 재사용하지 않은 이유?

  • 그 코드는 feature-migration-startup-contract 소유 + startup-exit(72) 시맨틱(= disabled required adapter 로 app 이 뜨면 실패). runtime invoke 는 lifecycle 이 다르다. 하나의 코드로 startup·runtime 두 의미를 표현하면 운영/런북이 혼동된다 → runtime 전용 ADAPTER_DISABLED(INTERNAL/500/retryable=false) 를 본 branch owner 로 신설. retryable=false 인 이유: 재배포 전까지 계속 disabled → 재시도로 안 풀리는 결정적 설정 버그(= INTERNAL_AUTH_MISCONFIGURATION 과 동류).

Q5. 왜 spring-kafka/lettuce 같은 실 SDK 를 안 넣었나?

  • skeleton 이 모든 선택형 adapter SDK 를 기본 탑재하면 무거워진다. 대신 KafkaSender/RedisClient/SlackClient/GoogleEmailClient 같은 integration seam(interface) 만 제공하고, 실제 client 구현 + SDK 의존은 해당 adapter 를 켜는 fork 프로젝트가 추가한다. 템플릿은 "실패/관측 계약 + on/off 메커니즘" 을 소유하고, 운영 연동은 소비자가 채운다.