4.0 KiB
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 |
|
|
|
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 메커니즘" 을 소유하고, 운영 연동은 소비자가 채운다.