97 lines
9.9 KiB
Markdown
97 lines
9.9 KiB
Markdown
---
|
|
title: Config & Adapter Templates (env-driven + optional module)
|
|
source_type: llm-generated
|
|
status: draft
|
|
confidence: medium
|
|
tags: [12-factor, config, spring-boot, adapter, conditional-on-property]
|
|
related_projects: [ca-skeleton]
|
|
last_reviewed: 2026-05-22
|
|
---
|
|
|
|
# Config & Adapter Templates (env-driven + optional module)
|
|
|
|
> Layer: `wiki/concepts/` — env 기반 runtime configuration과 optional adapter template를 동시에 다루는 일반 개념 문서. 구체적인 프로젝트 결정은 [[raw/project-notes/ca-skeleton-operational-contract]] §9 및 [[raw/branch-notes/feature-env-driven-runtime-configuration]], [[raw/branch-notes/feature-integration-adapter-templates]] 참조.
|
|
|
|
## Summary
|
|
|
|
**Env config**: 12-factor §III. Config 원칙을 따라 application-owned env에 `APP_` prefix, Duration은 `30s` 형식 1택, boolean은 `true/false` only, runtime reload는 기본 금지, `.env.example` drift 검증 도구로 누락 감지를 강제하는 설계.
|
|
|
|
**Adapter templates**: 선택형 adapter(Kafka/Redis/Slack/Email)는 기본 dependency가 아닌 optional module로 두고, `@ConditionalOnProperty` 3-layer(Layer 1 Spring bean 등록 조건, Layer 2 ArchUnit static dependency 검사, Layer 3 runtime `AdapterDisabledException` fail-fast)로 disabled adapter가 use case path에 새지 않게 막는 설계.
|
|
|
|
## Standard (공식 정의)
|
|
|
|
### Env-driven runtime configuration
|
|
|
|
- **12-factor §III. Config** — config는 코드와 분리된 환경 변수에 두고, 배포 환경별로 달라지는 값(자격 증명, hostname, profile)은 모두 env로 주입. config dump가 가능하면 안 됨.
|
|
- **Spring Boot externalized configuration** — `@ConfigurationProperties + @Validated`로 env 바인딩, `application.yml` profile-specific override, Spring `Duration` (`30s`/`PT30S`) / `DataSize` (`10MB`) 타입 지원.
|
|
- **검토된 대안**:
|
|
- **Spring Cloud Config Server** — 중앙 git-backed config + `@RefreshScope`로 runtime reload. config server 자체가 인프라 SPOF가 되고 bootstrap에 의존.
|
|
- **k8s ConfigMap + Spring Cloud Kubernetes auto-reload** — 3-level reload (`refresh` / `restart_context` / `shutdown`).
|
|
- **HashiCorp Consul KV** — KV store + watch.
|
|
- **AWS Parameter Store / AppConfig** — managed validator + CloudWatch auto-rollback + deployment strategy.
|
|
- **LaunchDarkly / Unleash** — feature flag SaaS. A/B/canary, user-targeting, percentage rollout 등 product-grade 기능 제공.
|
|
|
|
### Adapter templates (optional module)
|
|
|
|
- **Spring `@ConditionalOnProperty`** — `name`/`havingValue` 조건이 일치할 때만 bean 등록. Spring Boot 3.5.0+에서 `@ConditionalOnBooleanProperty` 도입.
|
|
- **Spring Boot AutoConfiguration** — `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`에 등록된 `@AutoConfiguration` 클래스가 조건부 bean을 제공. custom starter의 표준 방식.
|
|
- **검토된 대안**:
|
|
- **Java SPI / `ServiceLoader`** — `META-INF/services/<interface>`에 구현체 등록, classpath에서 발견된 모든 provider를 load.
|
|
- **Spring `@Profile` 기반** — profile 활성화로 bean 선택.
|
|
- **OSGi plugin architecture** — runtime module 동적 load/unload.
|
|
- **Feature flag library (FF4J / Togglz)** — runtime flag로 코드 path 분기.
|
|
|
|
## 한계 / 주의점
|
|
|
|
### Env config
|
|
|
|
- **12-factor env (process env 노출)** — secret이 process env에 남아 `/proc/<pid>/environ`, container metadata API, `env` actuator endpoint로 leak 가능. secret manager 별도 필요.
|
|
- **Spring Cloud Config Server** — 인프라 SPOF. config server 장애 시 client startup 차단 (bootstrap 의존).
|
|
- **k8s ConfigMap auto-reload** — pod별로 reload 타이밍이 다르면 partial-state가 생겨 디버깅 어려움. k8s lock-in 발생.
|
|
- **AWS AppConfig** — AWS lock-in + per-call billing.
|
|
- **LaunchDarkly / Unleash** — 외부 SaaS 의존, flag debt(제거되지 않은 flag 누적), cost. product-grade A/B/canary 요구가 발생하기 전에는 over-engineering.
|
|
|
|
### Adapter templates
|
|
|
|
- **Spring `@ConditionalOnProperty` Layer 1** — Spring 공식이 cover하는 영역은 bean 등록 조건뿐. application code가 disabled adapter package를 import해도 Spring 자체는 막지 못함.
|
|
- **ArchUnit Layer 2** — 별도 source가 필요한 미흡 영역. `noClasses().that().resideInAPackage("..application..").should().dependOnClassesThat().resideInAPackage("..adapters.{disabled}..")` 같은 정적 rule을 작성해야 하며, ca-tmpl 자체 contract로 G-I 후속 보강 대상.
|
|
- **ArchUnit Layer 2 정적 검사 한계** (2026-05-22 보강) — ArchUnit User Guide의 `DescribedPredicate` / `ArchCondition` API와 `JavaClass.getAnnotationOfType(...)`로 정적 추출 가능한 것은 (a) adapter 후보 class가 `@ConditionalOnProperty`를 부착했는지, (b) `name`/`havingValue` parameter 값이 `app.adapter.<name>.enabled` 패턴을 따르는지, (c) application layer가 adapter package를 직접 import하지 않는지(CA 경계)까지. **"현재 빌드/배포 환경에서 어떤 adapter가 실제 disabled인지"는 runtime config 평가이므로 ArchUnit 능력 밖**이며, Layer 3 (`AdapterDisabledException` runtime fail-fast)에 위임해야 함. 즉 Layer 2는 "annotation 존재 + naming pattern 강제" fitness function까지가 실효 범위. 자세한 평가는 [[raw/official-docs/archunit-conditional-on-property-3-layer-pattern]] 참조. status `needs-confirmation`.
|
|
- **`AdapterDisabledException` Layer 3** — branch 자체 contract. 표준 라이브러리가 제공하지 않으며 직접 구현.
|
|
- **Java SPI** — on/off boolean 표현 불가(classpath 존재 = enable), default constructor 강제, Spring DI 미통합. ca-tmpl의 `APP_ADAPTER_*_ENABLED` 결정과 정면 충돌.
|
|
- **Togglz / FF4J** — runtime branching tool로, startup-time adapter on/off와 시맨틱이 다름. ca-tmpl `@ConditionalOnProperty`(startup 결정)와 feature flag service(runtime 결정)는 분리 영역으로 취급해야 함.
|
|
|
|
## Project Application
|
|
|
|
- [[wiki/projects/ca-tmpl/config-and-adapter-templates]] — ca-tmpl 의사결정 기록 (현재 `documented-only`, Phase C2 미진입). 실제 구현 여부는 project 문서 참조.
|
|
- [[raw/branch-notes/feature-env-driven-runtime-configuration]] — `APP_` prefix, Duration `30s`, boolean `true/false`, no-runtime-reload, `.env.example` drift 검증 결정.
|
|
- [[raw/branch-notes/feature-integration-adapter-templates]] — optional module + `@ConditionalOnProperty` 3-layer detection + `AdapterDisabledException` fail-fast 결정.
|
|
- [[raw/project-notes/ca-skeleton-operational-contract]] — §9 Env-driven Runtime Configuration, §11 Adapter Failure Contract, §29 Group G-I.
|
|
|
|
## Interview Questions
|
|
|
|
- 12-factor §III. Config가 의미하는 "config와 코드 분리"는 구체적으로 무엇을 강제하는지 설명해 주세요.
|
|
- runtime config reload를 기본 금지(no-runtime-reload)로 결정한 근거와, 그 결정이 운영에서 갖는 trade-off는 무엇인가요?
|
|
- `@ConditionalOnProperty` 3-layer 검출(Spring bean 조건 + ArchUnit static + runtime fail-fast)이 각각 어떤 실패 시나리오를 잡아내려는 것인지 설명해 주세요.
|
|
- Java SPI `ServiceLoader`와 Spring `@ConditionalOnProperty`는 adapter on/off 표현에서 어떤 차이가 있나요?
|
|
- LaunchDarkly 같은 feature flag SaaS와 `@ConditionalOnProperty` 기반 startup toggle은 어떤 운영 요구가 생겼을 때 갈라지는지 설명해 주세요.
|
|
|
|
## Do Not Overclaim
|
|
|
|
- "`@RefreshScope`만 도입하면 dynamic config가 된다" 같은 단정은 피해야 함. ca-tmpl은 runtime reload를 기본 금지로 두며, reload가 필요한 경우는 secret manager + startup validation을 별도 branch로 분리하는 것이 결정 사항.
|
|
- "`@ConditionalOnProperty` 3-layer가 disabled adapter 호출을 완전 검증한다"고 단정하면 안 됨. Layer 1만 Spring 공식 cover이고, Layer 2(ArchUnit)는 source 부재로 G-I 후속 보강 대상, Layer 3(`AdapterDisabledException`)는 branch 자체 contract.
|
|
- "12-factor env가 secret 관리까지 책임진다"는 표현은 과장. process env 노출 위험은 12-factor 자체가 해결하지 않으며 secret manager가 별도 책임.
|
|
- "ca-tmpl이 LaunchDarkly/Togglz를 거부했다"가 아니라 "ca-tmpl scope에서 위임한 영역"이라는 표현이 정확.
|
|
|
|
## Sources
|
|
|
|
- [The Twelve-Factor App — III. Config](https://12factor.net/config) — [[raw/official-docs/config-12-factor-app-config]]
|
|
- [Spring Cloud Config (official)](https://docs.spring.io/spring-cloud-config/reference/) — [[raw/official-docs/config-spring-cloud-config-server-official]]
|
|
- [Spring Cloud Kubernetes — ConfigMap auto-reload](https://docs.spring.io/spring-cloud-kubernetes/reference/) — [[raw/official-docs/config-spring-cloud-kubernetes-configmap-reload]]
|
|
- [AWS AppConfig — Feature flag & deployment strategy](https://docs.aws.amazon.com/appconfig/) — [[raw/official-docs/config-aws-appconfig-feature-flag-deployment]]
|
|
- [LaunchDarkly — Feature flag best practice](https://launchdarkly.com/) — [[raw/company-tech-blogs/config-launchdarkly-feature-flag-best-practice]]
|
|
- [Spring Boot — Custom AutoConfiguration / starter](https://docs.spring.io/spring-boot/reference/features/developing-auto-configuration.html) — [[raw/official-docs/adapter-spring-boot-autoconfig-custom-starter]]
|
|
- [Java SPI — `java.util.ServiceLoader`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/ServiceLoader.html) — [[raw/official-docs/adapter-java-spi-serviceloader]]
|
|
- [Togglz / FF4J — Feature toggle library](https://www.togglz.org/) — [[raw/company-tech-blogs/adapter-togglz-ff4j-feature-toggle-library]]
|
|
- [ArchUnit — Writing Custom Rules / Accessing Annotation](https://www.archunit.org/userguide/html/000_Index.html) — [[raw/official-docs/archunit-conditional-on-property-3-layer-pattern]] (Layer 2 정적 검사 가능 범위 평가, needs-confirmation)
|
|
- Canonical: [[raw/project-notes/ca-skeleton-operational-contract]] (§9, §11, §29 Group G-I)
|