Files
llm-wiki/wiki/projects/ca-tmpl/config-and-adapter-templates.md

128 lines
10 KiB
Markdown

---
title: ca-tmpl - Config & Adapter Templates 결정 (env-driven + ConditionalOnProperty)
source_type: project
status: verified
confidence: high
tags: [ca-tmpl, 12-factor, config, conditional-on-property, actually-implemented, locally-verified]
related_projects: [ca-tmpl]
last_reviewed: 2026-07-02
---
# ca-tmpl - Config & Adapter Templates 결정 (env-driven + ConditionalOnProperty)
> Layer: `wiki/projects/` — ca-tmpl skeleton 프로젝트의 Config & Adapter 영역 결정 사항. 일반 개념은 [[wiki/concepts/config-and-adapter-templates]] 참조.
## 프로젝트 컨텍스트
**ca-tmpl skeleton** — Clean Architecture 기반 Spring Boot 템플릿. 신규 백엔드 서비스를 시작할 때 use case / port / adapter 경계, env-driven config, optional adapter on/off, 운영 contract(observability / failure / supply chain 등)를 미리 fix해 두는 사내용 skeleton.
본 문서가 다루는 영역(canonical §9 Env-driven Runtime Configuration, §29 Group G-I Adapter Failure & Disabled):
- **Env config 결정**: `APP_` prefix + Duration `30s` 형식 1택 + boolean `true/false` only + no-runtime-reload + `.env.example` drift 검증.
- **Adapter on/off 결정**: optional module + `@ConditionalOnProperty` 3-layer detection (Spring bean + ArchUnit static + `AdapterDisabledException` runtime fail-fast).
**진행 상황**: C2 부분 구현 + 로컬 검증 완료. `docs/registries/env-keys.yaml`, `verifyEnvKeys`, `@ConfigurationProperties` settings, startup safety validator, optional-adapter 조건부 테스트/ArchUnit guard가 존재한다. 모든 provider-specific adapter template가 구현된 것은 아니다.
## 실제 구현 내용 (`actually-implemented`)
- `docs/registries/env-keys.yaml`과 Gradle `verifyEnvKeys` gate가 존재한다.
- `app-bootstrap`, `adapter-web`, `sample-portfolio` 등에 `@ConfigurationProperties` 기반 `*Settings` 타입이 존재한다.
- `StartupSafetyValidator`, `RuntimeNumericBoundsValidator`, `RequiredEnvironmentValidator`, `RequiredAdapterDisabledException`이 startup fail-fast guard를 구성한다.
- `DisabledAdapterArchitectureTest`가 optional adapter bean의 `@ConditionalOnProperty` 부착과 disabled-default boundary를 정적으로 검증한다.
- `EnabledIfRedisCacheEnabled`, `EnabledIfHttpRetryEnabled`, `EnabledIfHttpCircuitBreakerEnabled` 등 optional adapter contract test 조건부 실행 annotation이 존재한다.
## 로컬/dev 검증 (`locally-verified`)
- `./gradlew check` 통과(2026-07-02, `BUILD SUCCESSFUL`, 114 tasks).
- 실행 중 `verifyEnvKeys: OK — 99 env keys, 72 required placeholders covered, 85 APP_ keys registered`가 출력되었다.
- `EnvProfileMatrixContractTest`, `StartupSafetyValidatorTest`, `RuntimeNumericBoundsValidatorTest`, `DisabledAdapterArchitectureTest`가 env/profile/optional adapter contract를 검증한다.
## 운영 검증 (`prod-verified`)
**없음.** ca-tmpl은 skeleton이며 prod 배포 이력 없음.
## 문서/계획만 존재 (`documented-only` / `planned`)
다음 결정은 구현된 gate와 아직 provider-specific adapter template로 남은 부분을 함께 기록한다.
### Env config (canonical §9)
- **`APP_` prefix** — application-owned env는 `APP_` 접두사로 통일, 외부 의존 env(`SPRING_*`, `JAVA_OPTS` 등)와 시각적 분리.
- **Duration 1택** — Spring `Duration` 입력은 `30s` 형식으로 통일(ISO-8601 `PT30S` 금지). 동일 의미 두 표기가 공존하면 grep/diff 비용이 발생.
- **Boolean `true/false` only** — `1/0`, `yes/no`, `on/off` 금지. Spring `Binder`가 허용하더라도 contract 수준에서 1택.
- **No-runtime-reload** — `@RefreshScope`, Spring Cloud Config refresh endpoint, Spring Cloud Kubernetes auto-reload 모두 기본 금지. config 변경은 **재배포로만** 반영.
- **`.env.example` drift verify** — `@ConfigurationProperties`에 선언된 모든 env가 `.env.example`에도 존재해야 함을 빌드 단계에서 강제. 누락 시 build fail.
### 5종 대안 검토 (concept 문서 참조)
[[wiki/concepts/config-and-adapter-templates]]에서 다음 5종을 검토하고 ca-tmpl scope에서는 모두 채택하지 않기로 결정:
- Spring Cloud Config Server — config server SPOF + bootstrap 의존
- k8s ConfigMap + Spring Cloud Kubernetes auto-reload — pod별 partial-state + k8s lock-in
- HashiCorp Consul KV — KV+watch 운영 비용
- AWS Parameter Store / AppConfig — AWS lock-in + per-call billing
- LaunchDarkly / Unleash — product-grade A/B/canary 요구가 발생하기 전에는 over-engineering, ca-tmpl scope 밖
### Adapter templates (canonical §29 G-I)
- **Layer 1 — Spring `@ConditionalOnProperty`**: `APP_ADAPTER_<NAME>_ENABLED=true`일 때만 adapter bean 등록. optional module 자체는 dependency로 두지만 disabled 시 bean 등록 X.
- **Layer 2 — ArchUnit static detection**: `noClasses().that().resideInAPackage("..application..").should().dependOnClassesThat().resideInAPackage("..adapters.<disabled>..")` 형태의 정적 dependency rule. application code가 disabled adapter package를 import하는 것을 빌드 단계에서 차단.
- **Layer 3 — `AdapterDisabledException` runtime fail-fast**: disabled adapter가 어떤 경로로든 호출되면 즉시 `AdapterDisabledException`을 던져 silent failure 방지.
- **ArchUnit Layer 2 정적 검사 범위 명확화 (2026-05-22)** — annotation 존재까지만 정적 보장(`@ConditionalOnProperty` 부착 + `app.adapter.<name>.enabled` naming pattern), runtime active 여부 검사는 Layer 3 (`AdapterDisabledException`)에 위임. 자세한 평가는 [[raw/official-docs/archunit-conditional-on-property-3-layer-pattern]] (status: `needs-confirmation`).
Layer 1/2 일부와 startup fail-fast guard는 구현되어 있다. 다만 Kafka/Slack/Email 같은 모든 provider-specific adapter template와 runtime call path의 disabled sentinel은 범위별로 추가 확인이 필요하다.
## 면접에서 말할 수 있는 범위
### 자신 있게 답할 수 있는 질문
- "12-factor §III. Config가 의미하는 'config와 코드 분리'는 구체적으로 무엇을 강제하는가"
- "ca-tmpl이 no-runtime-reload를 기본 방침으로 둔 결정의 근거는?"
- "`@ConditionalOnProperty` 3-layer (Spring bean 조건 + ArchUnit static + runtime fail-fast)가 각각 어떤 실패 시나리오를 잡는지"
- "Java SPI `ServiceLoader``@ConditionalOnProperty`가 adapter on/off 표현에서 어떻게 다른지"
- "LaunchDarkly 같은 feature flag SaaS와 `@ConditionalOnProperty` startup toggle은 어떤 운영 요구가 생겼을 때 갈라지는지(trade-off)"
### 적당히 답할 수 있는 질문
- "`@RefreshScope`를 금지로 둔 이유" — 결정 근거는 설명 가능. 운영 데이터/사례는 없음.
- "Vault dynamic credential과 `@RefreshScope` 같은 runtime reload 메커니즘이 충돌하는 지점" — 개념적으로는 설명 가능. 직접 운영 경험 없음.
### 답하면 안 되는 질문 (모른다고 해야 함)
- "`@ConfigurationProperties` 검증을 운영 환경에서 어떻게 운용하는가" — 운영 경험 없음.
- "adapter on/off를 실제 환경에서 전환한 경험" — 없음. ca-tmpl은 skeleton 단계.
- "Layer 2 ArchUnit rule이 실제 빌드에서 어떤 위반을 잡았는가" — `DisabledAdapterArchitectureTest``./gradlew check` 통과 범위까지 답할 수 있음. 모든 provider adapter runtime path 검증은 별도 확인 필요.
## 과장 금지 지점
- **"`@RefreshScope`만 도입하면 dynamic config가 된다"** — ❌. ca-tmpl은 `@RefreshScope`를 기본 금지로 두는 결정을 했고, 본인은 dynamic config를 운영한 경험이 없음. "도입 가능" 정도로만 표현해야 함.
- **"`@ConditionalOnProperty` 3-layer가 disabled adapter 호출을 완전 검증한다"** — ❌. Layer 1/2와 startup fail-fast 일부는 검증됐지만, 모든 provider adapter runtime path까지 자동 보장한다고 쓰지 않는다.
- **"ca-tmpl이 LaunchDarkly를 거부했다"** — ❌. "ca-tmpl scope 밖으로 위임했다" / "product-grade A/B/canary 요구가 발생하면 별도 branch로 다룬다"는 표현이 정확.
- **"Config & Adapter 전체가 구현 완료"** — ❌. env registry/gate와 optional-adapter guard는 구현됐지만 provider별 adapter template 완성도는 범위별 확인이 필요하다.
### Blog-topic ingest: env/config/adapter 묶음 (2026-07-02)
- [[raw/blog-topics/env-example-drift-gate-gradle-2026-06-06]]: `.env.example` 중복 사본 대신 실제 `application.yml` placeholder와 tracked `.env` key surface를 대조하는 drift gate 글감. `verifyEnvKeys` 구현과 `./gradlew check` 통과를 근거로 blogify 가능하다.
- [[raw/blog-topics/spring-conditional-on-property-optional-adapter-template-2026-06-09]]: heavy SDK를 기본 dependency로 싣지 않고 optional adapter seam, disabled default, `@ConditionalOnProperty`, ArchUnit, disabled sentinel로 계약을 만드는 글감. 구현 범위는 optional-adapter guard와 startup fail-fast 일부로 제한한다.
- [[raw/blog-topics/spring-boot-3-configprops-record-multi-constructor-binding-2026-06-12]]: Spring Boot 3 record `@ConfigurationProperties`에 보조 생성자를 추가했을 때 constructor binding auto-detect가 깨질 수 있는 troubleshooting 글감. 공식 문서 근거 보강 전까지 일반화하지 않는다.
## 관련 개념
- [[wiki/concepts/config-and-adapter-templates]]
## Sources
- [[raw/project-notes/ca-skeleton-operational-contract]] (§9 Env-driven Runtime Configuration, §29 Group G-I Adapter Failure & Disabled)
- [[raw/branch-notes/feature-env-driven-runtime-configuration]]
- [[raw/branch-notes/feature-integration-adapter-templates]]
- [[raw/blog-topics/env-example-drift-gate-gradle-2026-06-06]] — env key drift gate 블로그 글감 raw seed.
- [[raw/blog-topics/spring-conditional-on-property-optional-adapter-template-2026-06-09]] — optional adapter template 블로그 글감 raw seed.
- [[raw/blog-topics/spring-boot-3-configprops-record-multi-constructor-binding-2026-06-12]] — Spring Boot 3 record configuration binding 블로그 글감 raw seed.
- [[raw/official-docs/archunit-conditional-on-property-3-layer-pattern]] — ArchUnit Layer 2 정적 검사 가능 범위 평가 (needs-confirmation)
## Cluster / 묶음
<!-- GENERATED: derived-blogs:start -->
- [[wiki/blog/ca-tmpl-config-and-adapter-templates-2026-07-02]]
<!-- GENERATED: derived-blogs:end -->