fix: 하네스 제거 및 keycloak 문서 보강
This commit is contained in:
@@ -1 +0,0 @@
|
||||
../../vault/20-evidence/official-docs/archunit-conditional-on-property-3-layer-pattern.md
|
||||
@@ -0,0 +1,152 @@
|
||||
---
|
||||
title: ArchUnit Custom Rule for @ConditionalOnProperty 3-Layer Adapter Enforcement
|
||||
source_type: official-doc
|
||||
url: https://www.archunit.org/userguide/html/000_Index.html
|
||||
archive_url:
|
||||
status: needs-confirmation
|
||||
confidence: medium
|
||||
related_branches: [feature-integration-adapter-templates]
|
||||
related_projects: [ca-tmpl]
|
||||
tags: [ca-config-adapter, archunit, conditional-on-property, fitness-functions]
|
||||
created: 2026-05-22
|
||||
last_reviewed: 2026-05-27
|
||||
---
|
||||
|
||||
# ArchUnit Custom Rule for @ConditionalOnProperty 3-Layer Adapter Enforcement
|
||||
|
||||
> Layer: `raw/official-docs/` — ArchUnit 공식 User Guide(custom rules, annotation 접근) 발췌와, `@ConditionalOnProperty` 기반 adapter on/off의 Layer 2(정적 검사) 가능 범위 평가. ca-tmpl `feature-integration-adapter-templates` 그룹 G-I의 외부 source 부재 보강.
|
||||
|
||||
## Parent / 활용 branch (필수)
|
||||
|
||||
| Branch | 이 자료가 정당화하는 결정 |
|
||||
|---|---|
|
||||
| [[raw/branch-notes/feature-integration-adapter-templates]] | Group G-I 의 Layer 2 (ArchUnit 정적 검사) 실효 정의 — "annotation 부착 강제 + naming convention + CA 경계" 까지로 한정, "disabled adapter 호출 차단"은 Layer 3 runtime 책임이라는 분리 근거 |
|
||||
|
||||
## 컨텍스트 / 왜 저장했는지
|
||||
|
||||
ca-tmpl `feature-integration-adapter-templates` (그룹 G-I)는 disabled adapter 검출을 3-layer로 정의함:
|
||||
|
||||
- **Layer 1 — Spring `@ConditionalOnProperty`**: bean 등록 조건. Spring 공식 cover.
|
||||
- **Layer 2 — ArchUnit static dependency 검사**: application code가 disabled adapter package에 의존하지 못하게 차단. **외부 source 부재**.
|
||||
- **Layer 3 — `AdapterDisabledException` runtime fail-fast**: silent failure 방지. branch 자체 contract.
|
||||
|
||||
Layer 2는 ArchUnit User Guide가 "`@ConditionalOnProperty` 기반 conditional bean을 정적으로 검증한다"는 명시적 패턴을 제시하지 않음. ca-tmpl이 자체 fitness function으로 발명해야 하므로, **무엇이 정적으로 가능하고 무엇이 불가능한지 경계**를 평가해 두는 raw 근거가 필요함.
|
||||
|
||||
## 출처 / Source
|
||||
|
||||
- 원본 URL: https://www.archunit.org/userguide/html/000_Index.html
|
||||
- "Writing Custom Rules" 섹션 (`DescribedPredicate`, `ArchCondition` API)
|
||||
- "Accessing Annotation With/Without Classpath" 섹션 (`getAnnotationOfType`, `JavaAnnotation.get("value")`)
|
||||
- 보조 참조:
|
||||
- Spring Boot Reference — `@ConditionalOnProperty` (`name`, `havingValue`, `matchIfMissing`)
|
||||
- *Building Evolutionary Architectures* (Ford / Parsons / Kua) — "fitness function"의 개념적 출처
|
||||
- 아카이브 URL: (미수집)
|
||||
- 저자 / 조직: ArchUnit 프로젝트 (TNG Technology Consulting)
|
||||
- 발행 상태: ArchUnit User Guide는 v1.4.x 기준 지속 갱신 (2026-04 기준 최신)
|
||||
- 마지막 확인일: 2026-05-27
|
||||
|
||||
## 핵심 인용 / Key quotes (verbatim)
|
||||
|
||||
> [§Writing Custom Rules] "most architectural rules take the form: classes that ${PREDICATE} should ${CONDITION}" (implementation requires "exposing the concepts of `DescribedPredicate` and `ArchCondition`")
|
||||
|
||||
> [§Accessing Annotation With/Without Classpath — classpath 없음] "you must rely on `JavaAnnotation<?> annotation = javaClass.getAnnotationOfType()` and `Object value = annotation.get("value")`"
|
||||
|
||||
> [§Accessing Annotation With/Without Classpath — classpath 있음] "this can be written way more naturally: `CustomAnnotation annotation = javaClass.getAnnotationOfType(CustomAnnotation.class); String value = annotation.value()`"
|
||||
|
||||
> [§Domain Objects, Reflection and the Classpath] "ArchUnit's own rule APIs never rely on the classpath though. Thus the evaluation of default rules and syntax combinations does not depend on whether the classes were imported from the classpath or some JAR / folder."
|
||||
|
||||
> [§Building Evolutionary Architectures (Ford et al.)] "Architectural fitness functions — any mechanism that provides an objective integrity assessment of some architectural characteristic(s)."
|
||||
|
||||
## Claims Extracted / 추출된 주장
|
||||
|
||||
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|
||||
|---|---|---|---|---|---|
|
||||
| AUCP-C1 | ArchUnit custom rule 의 표준 형식은 "classes that ${PREDICATE} should ${CONDITION}" 이며 `DescribedPredicate` + `ArchCondition` 의 조합으로 작성 | [§Writing Custom Rules] "most architectural rules take the form: classes that ${PREDICATE} should ${CONDITION}" | `official-vendor-doc` | ArchUnit custom rule 작성 환경 | runtime config (env, property) 평가가 이 PREDICATE/CONDITION 으로 가능하다는 뜻은 아님 — bytecode 기반 정적 검사에 한정 |
|
||||
| AUCP-C2 | classpath 가 있을 때 annotation 접근은 `javaClass.getAnnotationOfType(CustomAnnotation.class)` + `.value()` 로 자연스럽게 가능 | [§Accessing Annotation With/Without Classpath — classpath 있음] "`CustomAnnotation annotation = javaClass.getAnnotationOfType(CustomAnnotation.class); String value = annotation.value()`" | `official-vendor-doc` | classpath 가 ArchUnit 평가에 포함된 환경 | classpath 없이 동일 ergonomics 가 가능하다는 뜻은 아님 — classpath 없을 때는 `JavaAnnotation<?>` + `.get("value")` 패턴 필요 |
|
||||
| AUCP-C3 | classpath 가 없을 때 annotation 접근은 `JavaAnnotation<?> annotation = javaClass.getAnnotationOfType()` + `Object value = annotation.get("value")` 로 수행 | [§Accessing Annotation With/Without Classpath — classpath 없음] "you must rely on `JavaAnnotation<?> annotation = javaClass.getAnnotationOfType()` and `Object value = annotation.get("value")`" | `official-vendor-doc` | classpath 없이 bytecode-only 분석 환경 | reflection 없이 strongly-typed accessor 가 가능하다는 뜻은 아님 — `Object` 로 반환 |
|
||||
| AUCP-C4 | ArchUnit 자체 rule API 는 classpath 에 의존하지 않으며, default rule + syntax 조합 평가는 classpath 에서 import 했는지 JAR/folder 에서 했는지에 무관 | [§Domain Objects, Reflection and the Classpath] "ArchUnit's own rule APIs never rely on the classpath though. Thus the evaluation of default rules and syntax combinations does not depend on whether the classes were imported from the classpath or some JAR / folder." | `official-vendor-doc` | ArchUnit default rule 평가 | custom annotation 접근까지 모두 classpath 독립이라는 뜻은 아님 — `.value()` ergonomics 는 classpath 필요 |
|
||||
| AUCP-C5 | *Building Evolutionary Architectures* 의 fitness function 정의는 "아키텍처 특성에 대한 객관적 무결성 평가를 제공하는 **모든 mechanism**" | [§Building Evolutionary Architectures (Ford et al.)] "any mechanism that provides an objective integrity assessment of some architectural characteristic(s)." | `engineering-blog` *(서적 출처)* | fitness function 개념 일반 | fitness function 이 runtime config 평가를 정의에 포함한다는 뜻은 아님 — mechanism 의 범위 정의는 책에 명시되지 않음 |
|
||||
|
||||
## Usage Boundaries / 적용 경계
|
||||
|
||||
- **이 자료가 직접 증명하는 것**:
|
||||
- `AUCP-C1`: custom rule 의 표준 형식 (PREDICATE + CONDITION)
|
||||
- `AUCP-C2`: classpath 있을 때의 annotation 접근 ergonomics
|
||||
- `AUCP-C3`: classpath 없을 때의 annotation 접근 API
|
||||
- `AUCP-C4`: ArchUnit default rule API 의 classpath 독립성
|
||||
- `AUCP-C5`: fitness function 의 개념 정의 (mechanism)
|
||||
- **이 자료가 증명하지 않는 것**:
|
||||
- "현재 빌드/배포 환경에서 특정 property 가 `false` 인지" 를 ArchUnit 이 정적으로 검증할 수 있다는 명제 (runtime config 영역 — ArchUnit 능력 밖)
|
||||
- "disabled 상태에서 application code 가 실제로 adapter 를 호출하는지" 를 ArchUnit 이 검증할 수 있다는 명제 (Spring container wiring runtime 결과)
|
||||
- profile/test profile 별 활성 adapter 를 ArchUnit 으로 판정할 수 있다는 명제
|
||||
- "annotation 부착 강제 + naming convention" 검사가 "disabled 호출 차단" 과 동등하다는 명제 (서로 다른 보장 수준)
|
||||
- "3-layer 가 disabled adapter 호출을 완전 검증한다" 는 명제 (Layer 3 runtime 까지 필요)
|
||||
- **내 프로젝트(ca-tmpl) 에 적용하려면 추가 확인이 필요한 것**:
|
||||
- ArchUnit Layer 2 가 ca-tmpl 의 어떤 정확한 fitness function 으로 구현되는지 (Phase C2 진입 시 코드로 검증)
|
||||
- `@ConditionalOnBooleanProperty` (3.5.0+) 사용 시 annotation 접근 방식이 동일한지 (classpath 의존성)
|
||||
- bytecode-only 환경 (Gradle build script 같은) 에서 `JavaAnnotation.get("name")` 호출의 안정성
|
||||
|
||||
## ArchUnit이 정적으로 추출할 수 있는 것 / 없는 것 (내 프로젝트 해석 — 미검증)
|
||||
|
||||
> 본 섹션은 자료 직접 인용 아님. ca-tmpl 자체 분석.
|
||||
|
||||
### 정적 추출 가능 (bytecode 기준)
|
||||
|
||||
- 어떤 class가 `@ConditionalOnProperty` annotation을 **부착했는지 여부** — `javaClass.isAnnotatedWith(ConditionalOnProperty.class)`.
|
||||
- 그 annotation의 **`name`, `havingValue`, `prefix`, `matchIfMissing` parameter 값** — `getAnnotationOfType(...)`로 enum/String 값 읽기 가능.
|
||||
- `noClasses().that().resideInAPackage("..application..").should().dependOnClassesThat().resideInAPackage("..adapters.<X>..")` 형태의 **package-level 정적 의존 검사** — ArchUnit 기본 API.
|
||||
- "adapter 후보 package에 있는 모든 `@AutoConfiguration` / `@Configuration` class는 `@ConditionalOnProperty`를 가져야 한다" 같은 **annotation 존재 강제 규칙** — custom `ArchCondition`으로 구현 가능.
|
||||
- "`@ConditionalOnProperty`의 `name`은 `app.adapter.<name>.enabled` 패턴을 따라야 한다" 같은 **naming convention 강제** — `annotation.get("name")` 값을 정규식으로 검사.
|
||||
|
||||
### 정적 추출 **불가능** (runtime 정보)
|
||||
|
||||
- **"현재 빌드/배포 환경에서 `app.adapter.kafka.enabled`가 실제로 `false`인지"** — 이는 runtime config(env, `application.yml`, `--args`)에 의존. bytecode에는 존재하지 않음.
|
||||
- **"disabled 상태에서 application code가 실제로 adapter를 호출하는지"** — Spring container의 실제 bean wiring 결과는 runtime에 결정.
|
||||
- **"profile/test profile/local profile별로 어떤 adapter가 활성화되는지"** — Spring Environment resolver의 runtime 동작.
|
||||
|
||||
### 부분 가능 (조합형 정적 검사)
|
||||
|
||||
- **"application layer가 adapter package를 import하지 않는다"** — 정적 가능. 단, "현재 adapter가 disabled여서" 막는 게 아니라 "**hexagonal/CA 경계상 항상 직접 의존 금지**"로 재해석해야 의미가 있음.
|
||||
- **"port interface를 통해서만 adapter를 호출한다"** — 정적 가능. CA 경계 강제와 동일한 규칙.
|
||||
- **"disabled 시 호출되는 모든 adapter 진입점은 `AdapterDisabledException`을 throw할 수 있게 선언/구현돼 있다"** — `JavaMethod`의 throws 절이나 method body call 검사로 부분 가능. 단, "실제 호출 시 throw하는지"는 runtime.
|
||||
|
||||
## Layer 2 정적 검사의 실제 가능 범위 — 결론 (내 프로젝트 해석)
|
||||
|
||||
ArchUnit Layer 2가 정적으로 **보장 가능한 범위**는 다음 3가지뿐:
|
||||
|
||||
1. **annotation 부착 강제**: adapter 후보 class가 `@ConditionalOnProperty`(또는 3.5.0+ `@ConditionalOnBooleanProperty`)를 가지는가.
|
||||
2. **naming convention 강제**: 그 annotation의 `name` 값이 `app.adapter.<name>.enabled` 패턴을 따르는가.
|
||||
3. **CA 경계 강제** (별도 목적): application layer가 adapter package를 직접 import하지 않는가 — 이는 "disabled 검출"이 아니라 hexagonal 경계 자체.
|
||||
|
||||
**보장 불가능한 범위**:
|
||||
|
||||
- "현재 disabled인 adapter가 실제로 호출되지 않는다" — runtime config + Spring container 동작이 결합돼야 판정 가능. **Layer 3 (`AdapterDisabledException` runtime fail-fast)에 위임**.
|
||||
- "특정 profile에서 어떤 adapter가 활성화되는지" — runtime resolver 영역.
|
||||
|
||||
### 따라서 ca-tmpl Layer 2의 실효 정의
|
||||
|
||||
ca-tmpl Layer 2는 "adapter 후보 class가 `@ConditionalOnProperty` 부착 + 표준 naming pattern을 따른다"는 **fitness function**으로 한정해야 함. "disabled adapter가 호출되지 않는다"는 명제까지 확장하면 ArchUnit 능력 밖이며, **실제 disabled 시 호출 차단은 Layer 3 runtime 책임**.
|
||||
|
||||
이 한계를 명시하지 않으면 "3-layer가 disabled adapter 호출을 완전 검증한다"는 **과장**으로 이어짐.
|
||||
|
||||
## 메모 / Notes (내 프로젝트 해석 — 미검증)
|
||||
|
||||
- ArchUnit은 "fitness function" 개념(Building Evolutionary Architectures)의 대표 Java 구현체 중 하나. 그러나 fitness function 자체가 runtime config 평가를 포함한다는 정의는 없음. ArchUnit의 범위는 bytecode 정적 분석.
|
||||
- Spring Boot AutoConfiguration의 `@ConditionalOn*` 평가는 **Spring container startup 시점**이지, 빌드 시점이 아님. 따라서 "disabled 시 bean이 등록되지 않는다"의 검증은 ApplicationContext 기반 통합 테스트(Layer 1 verification)에서 수행해야 함.
|
||||
- 정적 추출이 가능한 부분(`@ConditionalOnProperty` 부착 강제)도 **결정은 코드 단계에서 fitness function으로 도입할지 보류 가능**. ca-tmpl Phase C2 진입 전에는 contract 수준 결정만 유지.
|
||||
|
||||
## Related / 관련
|
||||
|
||||
- 같은 주제 다른 official-doc:
|
||||
- [[raw/official-docs/governance-archunit-official]] (ArchUnit 공식 소개)
|
||||
- [[raw/official-docs/archunit-annotation-as-registry-evaluation]] (annotation-as-registry 대안 평가)
|
||||
- [[raw/official-docs/adapter-spring-boot-autoconfig-custom-starter]] (Layer 1 — Spring `@ConditionalOnProperty` 공식 시맨틱)
|
||||
- [[raw/official-docs/adapter-java-spi-serviceloader]] (대안 4 — Java SPI)
|
||||
- 인용하는 branch:
|
||||
- [[raw/branch-notes/feature-integration-adapter-templates]] (그룹 G-I)
|
||||
- canonical contract 섹션:
|
||||
- [[raw/project-notes/ca-skeleton-operational-contract]] §11 Adapter Failure Contract, §29 Group G-I
|
||||
- 관련 wiki:
|
||||
- [[wiki/concepts/config-and-adapter-templates]] (작성 시 — Adapter templates 한계 섹션)
|
||||
- [[wiki/projects/ca-tmpl/config-and-adapter-templates]] (작성 시 — documented-only 결정 기록)
|
||||
- 본 source의 위치: Layer 2 (ArchUnit static detection) 정적 검사 가능 범위 평가 — 외부 source 부재 보강
|
||||
Reference in New Issue
Block a user