refactor: 문서 개선 중

This commit is contained in:
donghyeon-ka
2026-09-21 14:30:55 +09:00
parent c93cdea150
commit 805a18f486
1497 changed files with 525837 additions and 59152 deletions
@@ -47,7 +47,7 @@ mongo 플랫폼 자동설정에서 트랜잭션 타입과 인과 세션 타입
데이터 위험은 없다. 없는 것을 쓸 수는 없기 때문이다. 이 플래그가 무엇을 켜는지 적힌 곳이 없고, 시작 검증이 통과한 것과 실행체가 조립된 것을 구분해 주는 신호도 없다.
수정은 셋 중 하나다. 트랜잭션 실행체를 조건부 빈으로 조립하거나, 플래그가 무엇을 켜는지 문서에 적거나, 같은 생성자의 required-secondaries 처럼 값을 예외로 거부하는 것이다.
수정 선택지는 셋이다. 트랜잭션 실행체를 조건부 빈으로 조립할 수 있고, 플래그가 실제로 무엇을 켜는지 문서화할 수 있으며, 같은 생성자의 `required-secondaries`처럼 지원하지 않는 값을 예외로 거부할 수도 있다.
## 검증 환경
@@ -87,9 +87,9 @@ Spring Boot : 4.0.8
## 그 검사 자체가 조건부다
검증기를 돌리는 빈은 `MongoTopologyProbe` 에 조건되어 있다. 그리고 이 저장소 프로브를 출하하지 않는다 — 그 자리 javadoc 이 직접 적는다. 프로브는 연결을 소유한 조립 루트가 살아 있는 데이터 평면 클라이언트로 만드는 것이고, 그것은 fork 결정이라는 것이다.
검증기를 돌리는 빈은 `MongoTopologyProbe`에 조건되어 있다. 이 저장소 프로브를 출하하지 않는다는 사실은 `MongoTopologyProbe`의 javadoc에 적혀 있다. 연결을 소유한 조립 루트가 실제 데이터 평면 클라이언트로 프로브를 만들며, javadoc은 그 선택을 fork의 책임으로 둔다.
프로브 없이 플랫폼 프로파일만 설정한 배포는 별도의 빈이 시작을 거부한다. 예외 문구가 이유를 적는다 — 검사가 하필 자기 부재를 보고해야 할 바로 그 빈에 조건되어 있어, 프로브 없이 시작하면 토폴로지Stable API 수준자격의 실제 능력도 아무것도 검사하지 않은 채 조용히 지나간다는 것이다.
프로브 없이 플랫폼 프로파일만 설정한 배포는 별도의 빈이 시작을 거부한다. 예외 문구는 검증 빈 자체가 프로브에 조건되어 있어, 프로브면 토폴로지·Stable API 수준·자격의 실제 능력을 검사할 경로가 열리지 않는다고 설명한다.
그래서 이 플래그가 시작 요구를 만드는 것은 fork 가 프로브와 보안 프로파일과 관리 자격 참조와 스키마 버전 범위를 모두 공급했을 때다. 셰이프 그대로의 이 저장소에서는 검사가 열리지 않는다.
@@ -99,7 +99,7 @@ Spring Boot : 4.0.8
`profiles` 의 널은 빈 맵으로 흡수한다. `change-streams` 는 무엇이 오든 거짓으로 덮어쓴다. `required-secondaries` 에 음수가 오면 예외를 던져 거부한다.
`transactions` 는 이 생성자에 아예 등장하지 않는다. 값이 그대로 보존되는 이유가 그것이다.
`transactions`는 이 생성자에 아예 등장하지 않는다. 생성자가 이 값을 덮어쓰거나 거부하지 않으므로 입력값이 그대로 보존된다.
덮어쓰기 쪽만 참으로 설정해도 예외도 로그도 발생하지 않는다. 그 줄에 붙은 주석은 값을 무시하면 적용된 것처럼 보이게 되니 저장하지 않고 거부한다고 적는데, 예외로 거부하는 것은 `required-secondaries` 가 하는 일이고 여기서 일어나는 것은 조용한 덮어쓰기다.
@@ -113,7 +113,7 @@ Spring Boot : 4.0.8
## 두 스위치가 반대 방향으로 같은 곳에서 끊겼다
트랜잭션은 스위치가 살아서 요구를 만드는데 그 요구를 갚을 코드가 조립되지 않는다. change stream 은 코드가 조립되는데 스위치가 죽어 있다. 방향은 반대이고 끊긴 자리는 같다.
트랜잭션은 스위치가 요구를 만들지만 그 요구를 수행할 코드가 조립되지 않는다. change stream은 실행 코드가 조립되는데 스위치가 생성자에서 꺼진다. 방향은 반대지만 둘 다 설정 플래그와 실제 조립 경로가 분리되어 있다.
## 남는 것은 데이터 위험이 아니다
@@ -65,10 +65,10 @@ kafka-clients : 4.1.2
## 재현 조건
1. KafkaProfileValidator 에서 운영 프로파일에 거는 규칙 둘을 읽는다.
2. 그 검증기를 설정에서 컴파일 프로파일에 돌리는 자리와, 그이 기동의 어느 단계지 확인한다.
2. 설정에서 컴파일 프로파일을 검증기에 넘기는 호출 경로와, 그 호출이 기동의 어느 단계에서 실행되는지 확인한다.
3. 그 거부를 고정하는 테스트를 찾는다.
4. 두 조립부를 켜는 프로퍼티를 전부 찾고 출하 기본값을 읽는다.
5. 프로덕션에서 KafkaProducer 를 만드는 자리를 전부 고, 각 설정 맵의 원문을 그대로 읽는다.
5. 프로덕션에서 `KafkaProducer`를 생성하는 코드를 전부 고, 각 생성 코드가 넘기는 설정 맵을 그대로 읽는다.
6. security.protocol 을 상수명과 리터럴 양쪽으로 저장소 전체에서 찾는다.
7. KafkaSecurityConfigurer.configure 가 자격 종류마다 무엇을 넣고 어디서 던지는지 읽는다.
8. 그 클래스의 빈 팩토리에 붙은 조건을 따라가고, 사슬 끝의 인터페이스를 구현하는 main 클래스를 센다.
@@ -95,7 +95,7 @@ kafka-clients : 4.1.2
## 조립되는 KafkaProducer 두 곳에 security.protocol 이 없다
프로덕션에서 `KafkaProducer` 만드는 자리는 둘이다.
프로덕션에서 `KafkaProducer`생성하는 코드는 둘이다.
`KafkaMessagingAutoConfiguration.messagingKafkaProducer``:153` 에서 만든다. `:139` 에서 빈 `HashMap` 을 열고 `:140`\~`:152` 에 넣는 것은 `BOOTSTRAP_SERVERS_CONFIG`, 직렬화기 둘, `ACKS_CONFIG`, `ENABLE_IDEMPOTENCE_CONFIG` 다섯이다.
@@ -122,7 +122,7 @@ kafka-clients : 4.1.2
이 클래스를 만드는 팩토리는 `KafkaMessagingAutoConfiguration:109` 에 있는데, 조건이 두 단이다. `:107``@ConditionalOnBean(CredentialRuntimeRegistry.class)` 이고, 그 레지스트리를 내놓는 `MessagingCoreAutoConfiguration:317``:315``@ConditionalOnBean(CredentialProvider.class)` 뒤에 있다. 그런데 `CredentialProvider` 를 구현하는 main 클래스가 0 건이다. 유일한 구현은 `CredentialRuntimeRegistryTest:21` 의 시험용 클래스다.
그래서 출하되는 애플리케이션에서 이 빈 만들어지지 않는다. 만들어졌다 해도 받을 곳이 없다 — 그것을 파라미터나 필드로 받는 프로덕션 코드가 0 건이고, `getBean``ObjectProvider`빈 이름 문자열로 가져가는 자리도 0 건이다.
그래서 확인한 출하 조립 경로에서 이 빈 만들어지지 않는다. searched direct reference 기준으로 파라미터나 필드로 받는 프로덕션 코드가 0건이고, `getBean`·`ObjectProvider`·빈 이름 문자열로 조회하는 코드도 찾지 못했다.
## 조립 테스트가 설정 맵의 키를 단언하지 않는다
@@ -130,9 +130,9 @@ kafka-clients : 4.1.2
실 브로커에 붙는 `MessagingLiveRoundTripQualificationTest:66``new KafkaContainer("apache/kafka:4.1.0")` 를 쓴다. 보안 설정이 하나도 없는 컨테이너다. 그래서 이 테스트가 확인한 왕복은 보안 설정이 없는 브로커와의 왕복이다.
## 원문과 갈리는 자리
## 원문과 다른 생산자 수
원문 §17.1 은 조립되는 생산자를 `messagingKafkaProducer` 하나로 적었다. 프로덕션에서 `KafkaProducer` 를 만드는 자리는 둘이, `KafkaSenderConfig.kafkaSeamProducer` 도 같은 프로퍼티 조건에서 조립되며 그쪽에도 보안 키가 없다.
원문 §17.1은 조립되는 생산자를 `messagingKafkaProducer` 하나로 적었다. 실제 프로덕션 생성 코드는 둘이, `KafkaSenderConfig.kafkaSeamProducer`도 같은 프로퍼티 조건에서 조립된다. 이 두 번째 생산자 설정에도 보안 키가 없다.
원문이 `KafkaSecurityConfigurer` 가 만드는 성분을 다섯으로 센 것도 자격 종류를 하나로 놓았을 때다. `configure` 는 자격 종류마다 다른 SASL 메커니즘을 넣고, 상호 TLS 에서는 `sasl.jaas.config` 없이 `NONE` 만 넣으며, OAuth2 와 Nkey 에서는 아무것도 넣지 않고 던진다.
@@ -26,13 +26,13 @@ messaging 플랫폼의 outbox 사슬은 조건이 참이 될 수 없어 조립
## 관계
- **@Bean이 있다는 것은 조립 증거가 아니다**
두 스택 중 하나만 컨텍스트에 들어간다는 것이 이 규칙의 사례다.
이 사건에서는 두 스택 중 하나만 컨텍스트에 들어갔고, 빈 선언만으로 실제 조립 여부를 판단할 수 없었다.
- **@ConditionalOnBean은 조건이 만족될 수 있는지까지 확인해야 한다**
조건이 참이 될 수 없다는 관측이 이 규칙으로 이어진다.
- **조립 결함을 판정하려면 조립하는 쪽을 먼저 읽어야 한다**
조립하는 쪽을 읽지 않으면 중복을 조건 결함으로 오진한다.
- **high-water mark가 본 위치를 뜻해서 재전달된 변경이 영구히 사라졌다**
같은 신뢰성 계열에서 조용한 실패가 나타난 다른 사례다.
같은 신뢰성 계열에서 high-water mark 해석이 재전달 이벤트를 삼킨 별도 실패를 다룬다.
## 문제
@@ -66,7 +66,7 @@ messaging 스타터의 MessagingReliabilityAutoConfiguration 은 outbox 와 inbo
구현 : JdbcOutboxRepository 2,276 LOC. 스프링 스테레오타입이 없다
조립 : MessagingReliabilityAutoConfiguration 81행의 조건 뒤
측정으로 확정한 것은 이렇다. main 코드에서 JdbcOutboxRepositoryJdbcInboxRepositoryOutboxEnvelopeFactory 를 생성하는 곳이 0 이고, app-bootstrap 에서 관련 빈을 만드는 곳도 0 이다. OutboxEnvelopeFactory@Bean 으로 만드는 곳은 스타터 테스트 하나뿐이다.
정적 검색에서는 main 코드`JdbcOutboxRepository`·`JdbcInboxRepository`·`OutboxEnvelopeFactory`를 직접 생성하지 않았고, app-bootstrap에서 관련 빈 생성 코드를 찾지 못했다. `OutboxEnvelopeFactory``@Bean`으로 만드는 코드는 스타터 테스트 하나에서만 확인했다.
이 차이가 중요한 이유는 수정 방향이 반대이기 때문이다. 조건이 만족되지 않는다고 읽으면 app-bootstrap 에 빈을 등록하는 수정이 된다. outbox 가 둘이라고 읽으면 어느 쪽이 정본인지 먼저 정해야 하는 문제가 된다.
@@ -0,0 +1,70 @@
---
kind: CASE
slug: the-guard-is-on-and-the-service-is-not
title: 가드와 journal 과 durability 검증기는 켜지고, 부를 서비스가 없었다
topic: assembly-ownership
topicName: 조립 소유권 — 통제와 그 의존을 같은 곳이 소유하기
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: case:the-guard-is-on-and-the-service-is-not
source:
- final/document.md#a19
- final/document.md#5-4
- final/document.md#a19 §8.1
- final/document.md#a19 §8.2
---
# 가드와 journal 과 durability 검증기는 켜지고, 부를 서비스가 없었다
admin 경로의 보호 장치는 조립되는데 파괴적 작업을 수행할 서비스는 자동설정하지 않는 구조가 함께 존재한다. 현재 판정은 정적 조립 분석에 근거하며 실제 admin 활성 부팅은 재현하지 않았다.
## 관계
- **파괴적 admin 작업은 자동설정하지 않는다**
이 부재를 프로젝트가 의도한 결정으로 설명한다.
- **@Bean이 있다는 것은 조립 증거가 아니다**
보호 장치와 실제 실행 서비스를 분리해서 확인하는 기준이다.
## 문제
가드·journal·durability 검증 같은 주변 통제는 자동설정 경로에 올라가지만, 실제 파괴 작업을 수행하는 admin 서비스는 같은 방식으로 제공되지 않는다.
정적 분석에서 서비스 직접 참조와 조립 경로를 찾지 못했고, SSOT는 이 부재를 의도된 안전 경계로 기록한다. 문제는 보호 장치가 존재한다는 사실만 보고 admin 기능 전체가 제공된다고 오해할 수 있다는 점이다.
## 결론
이 구조는 “가드가 켜졌으니 서비스도 켜졌다”는 신호가 아니다. 파괴적 실행 서비스는 애플리케이션이 명시적으로 제공해야 하며, 플랫폼 자동설정은 그 서비스를 만들어 주지 않는다.
현재 머신에서 source repository를 다시 대조하지 못했으므로, 실제 admin 활성 부팅까지 확인했다고 주장하지 않는다.
## 검증 환경
sourceRevision : 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
현재 source repository 재대조 : UNVERIFIABLE
확인 방식 : SSOT에 기록된 조립 경로와 정적 참조 분석
## 재현 조건
1. SSOT의 admin 자동설정 절에서 생성되는 guard·journal·validator를 확인한다.
2. 파괴적 admin service 타입의 main 직접 참조와 생성 경로를 확인한다.
3. 자동설정이 실행 서비스를 직접 제공하는지 분리해서 본다.
4. 실제 부팅 재현은 source repository가 있는 환경에서 별도로 수행한다.
## 본문
<!-- body:start -->
## 보호 장치와 실행 서비스는 같은 능력이 아니다
가드는 호출을 허용할지 판정하고 journal은 실행 이력을 남긴다. durability validator는 전제 조건을 검사한다. 이 셋이 조립돼도 실제 파괴 작업을 수행할 service가 자동으로 생기지는 않는다.
## 부재를 의도된 경계로 읽는다
SSOT는 destructive admin operation을 플랫폼이 자동설정하지 않는 방향을 별도 Decision 후보로 분리한다. 따라서 서비스 부재는 현재 분석에서 우연한 누락으로만 다루지 않는다.
## 확인하지 못한 것
실제로 admin 기능을 켠 애플리케이션을 부팅해 “호출 대상이 없다”는 런타임 결과를 재현하지 않았다. 현재 결론은 sourceRevision에 대한 기존 정적 분석 범위다.
<!-- body:end -->
@@ -1,7 +1,7 @@
---
kind: CASE
slug: thirteen-startup-rules-never-run
title: 시작 검증기 13개 규칙이 유일한 조립 지점에서 호출지 않는다
title: 확인한 자동설정 경로가 시작 검증기 13개 규칙을 호출지 않는다
topic: assembly-ownership
project: clean-architecture-backend-template
status: 게시 전
@@ -17,14 +17,14 @@ source:
- 원본 분석 절은 final/document.md#5-3 · final/document.md#a20 §3.1 이다.
---
# 시작 검증기 13개 규칙이 유일한 조립 지점에서 호출지 않는다
# 확인한 자동설정 경로가 시작 검증기 13개 규칙을 호출지 않는다
gRPC 플랫폼의 시작 검증기는 188줄에 13개 위반 규칙을 담고 있다. 이 검증기를 부르는 것은 자기 테스트뿐이고, 유일한 자동설정 지점은 부르지 않는다.
gRPC 플랫폼의 시작 검증기는 188줄에 13개 위반 규칙을 담고 있다. searched direct reference 기준으로 호출은 자기 테스트에서 확인했고, `GrpcPlatformAutoConfiguration`은 이 검증기를 직접 호출하지 않는다. 이 결과만으로 다른 lifecycle·framework discovery 경로까지 없다고 단정하지 않는다.
## 관계
- **시작 검증기가 도는지는 그 능력에 자동설정 루트가 있는지와 일치한다**
이 사례에서 끌어낸 확인 절차다.
- **시작 검증기는 실제 lifecycle과 assembly 경로에서 실행 여부를 확인한다**
이 사례의 direct-call 결과를 runtime 전체 미실행으로 과장하지 않기 위해 만든 확인 절차다.
- **@Bean이 있다는 것은 조립 증거가 아니다**
검증기가 존재한다는 것과 그것이 도는 것은 별개다.
@@ -32,19 +32,19 @@ gRPC 플랫폼의 시작 검증기는 188줄에 13개 위반 규칙을 담고
GrpcPlatformStartupValidator 는 188줄이고 violations 목록에 13개 항목을 추가한다. TLS 요구, 실행기 풀 크기, 채널 프로파일, 자격증명 누출 등을 검사한다.
이 검증기를 호출하는 곳을 저장소 전체에서 찾으면 자기 테스트 GrpcPlatformStartupValidatorTest 하나뿐이다.
searched direct reference에서는 `GrpcPlatformStartupValidatorTest`의 테스트 호출만 확인된다.
같은 패키지의 GrpcPlatformAutoConfiguration 은 106줄이고 @Bean 이 9개인데, 그중 어느 것도 이 검증기를 부르지 않는다.
## 결론
검증기는 정확하고 잘 테스트되어 있으며 돌지 않는다.
검증기는 규칙과 단위 테스트를 갖고 있지만, 확인한 자동설정 경로에서는 호출되지 않는다.
이 판정은 gRPC 블록 전체가 어떤 런타임 컴포지션에 속하지 않는다는 더 큰 사실 안에 있다. 그 상태는 저장소 문서로 인정하고 있으며 결함이 아니다. 다만 이 검증기의 경우, 블록 배포기 시작하는 날에도 자동으로 돌기 시작하지는 않는다는 점이 남는다. 조립 지점이 그것을 부르지 않기 때문이다.
이 판정은 gRPC 블록 전체가 현재 확인한 런타임 컴포지션에 속하지 않는다는 범위 안에 있다. 저장소 문서도 그 상태를 인정하므로 현재 미실행 자체를 결함으로 보지 않는다. 다만 블록 배포기 시작할 때는 검증기를 lifecycle에 명시적으로 연결해야 한다. 확인한 자동설정 경로는 이 검증기를 호출하지 않는다.
13개 규칙은 테스트로 고정되어 있으므로 회귀는 잡힌다. 잡히지 않는 것은 그 규칙이 실행 시점에 적용되는가다.
확인 절차로 일반화하면 이렇다. 시작 검증기가 실제로 도는지는 그 능력에 자동설정 루트가 있고 그 루트가 검증기를 부르는지와 일치한다. 검증기 파일의 존재나 그 테스트의 통과는 답이 아니다.
확인 절차로 일반화하면 direct caller에서 멈추지 않는다. `@Bean`·component scan·auto-configuration, lifecycle callback, application event·post processor, framework discovery를 차례로 확인하고, 실제 부팅이 가능한 환경에서는 condition report와 시작 로그까지 본다. 검증기 파일과 테스트의 존재만으로 runtime 실행 여부를 판정하지 않는다.
## 검증 환경
@@ -73,13 +73,13 @@ validator가 5개 그룹 13개 규칙을 갖고(transport·security 4 / executor
:::evidence key="thirteen-startup-rules-never-run" alt="분석 문서 final/document.md 에서 이 기록의 근거 절을 그대로 잘라낸 18줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="final/document.md 발췌 — 18줄" zoom="true"
:::
## 유일한 조립 지점이 부르지 않는다
## 확인한 자동설정 경로는 validator를 부르지 않는다
자동설정은 `@Bean` 9개를 만들면서 이 validator를 부르지 않고, static 메서드라 빈이 될 수도 없다.
자동설정은 `@Bean` 9개를 만들면서 이 validator를 직접 부르지 않는다. validator 자체는 static 메서드 기반이라 일반적인 component bean 등록 경로도 보이지 않는다. 다만 다른 lifecycle·framework discovery 경로까지 이번 정적 검색으로 배제하지 않는다.
## 두 개의 강제가 이 하나를 통해서만 성립한다
CLAUDE.md가 인용한 "streaming method가 Stable catalog에 등록되면 startup을 거부한다"와 §2.2의 runtime 강제 둘 다이므로, 둘 다 실행되지 않는다.
CLAUDE.md가 인용한 "streaming method가 Stable catalog에 등록되면 startup을 거부한다"와 §2.2의 runtime 강제는 이 validator의 규칙에 의존한다. 확인한 자동설정 경로만 보면 이 규칙을 호출하지 않는다.
## 확인하지 못한 것
@@ -28,9 +28,9 @@ source:
## 관계
- **스캔에서 뺀 다섯 패키지의 컴포넌트 여섯을 두 자동설정 어느 쪽도 소유하지 않았다**
경로가 바뀌는 지점에서 소유권이 끊긴 사례다.
스캔에서 제외된 뒤 자동설정도 여섯 컴포넌트를 만들지 않아 실행 컨텍스트에 들어오지 않았다.
- **넓은 스캔을 좁히자 여덟 컴포넌트에 아무것도 도달하지 않았다**
같은 형태가 퍼시스턴스 리프에서 나타난 사례다.
퍼시스턴스 리프에서도 스캔 범위를 좁힌 뒤 여덟 컴포넌트의 조립 경로가 사라졌다.
- **@Bean이 있다는 것은 조립 증거가 아니다**
이 개념을 확인 절차로 옮긴 규칙이다.
@@ -0,0 +1,45 @@
---
kind: PROJECT_DECISION
slug: destructive-admin-operations-are-not-autoconfigured
title: 파괴적 admin 작업은 자동설정하지 않는다
topic: assembly-ownership
topicName: 조립 소유권 — 통제와 그 의존을 같은 곳이 소유하기
project: clean-architecture-backend-template
status: 게시 전
decisionStatus: ADOPTED
source:
- final/document.md#a19
- final/document.md#10-3
- final/document.md#5-4
- final/document.md#a19 §8.1
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
---
# 파괴적 admin 작업은 자동설정하지 않는다
플랫폼은 파괴적 admin 작업의 보호 장치와 계약을 제공할 수 있지만, 실제 실행 서비스까지 기본 빈으로 만들지는 않는다.
## 근거
- **가드와 journal 과 durability 검증기는 켜지고, 부를 서비스가 없었다**
보호 장치와 실행 주체가 분리된 현재 조립 결과를 보여 준다.
- **꺼짐은 조건의 반복이 아니라 구조여야 한다**
능력 활성 여부를 조립 구조로 제한하는 기준이다.
## 결정문
파괴적 admin operation의 실제 실행 서비스는 자동설정하지 않는다. 애플리케이션이 사용 의도를 명시하고 필요한 의존성을 제공한 경우에만 별도 조립 경로에서 만든다.
## 판단 이유
파괴 작업은 잘못 노출됐을 때 복구 비용이 크다. 플랫폼이 클래스패스와 설정만 보고 실행 서비스를 자동 생성하면, 사용자가 기능을 선택하지 않았는데도 파괴 권한이 생길 수 있다.
가드와 journal을 자동설정하는 것은 실행 서비스 자동설정과 다르다. 보호 장치는 실행 서비스가 제공되는 경우 적용할 공통 규칙이고, 서비스 생성은 채택 애플리케이션의 책임으로 둔다.
## 영향
감수하는 것 : 기능을 쓰는 애플리케이션이 명시적인 wiring을 추가해야 한다.
얻는 것 : 라이브러리를 추가했다는 이유만으로 파괴적 operation이 실행 가능한 상태가 되지 않는다.
얻는 것 : 자동설정의 존재와 admin 권한의 존재를 구분할 수 있다.
@@ -37,7 +37,7 @@ source:
그리고 자식을 컴포넌트 스캔에서 빼는 것이 이 구조의 나머지 절반이다. 스캔이 자식 설정을 독립적으로 발견하면 루트를 우회하기 때문이다.
임포트 필터는 권한을 잃고 도구로 남는다. 프레임워크 자신의 자동설정을 후보 집합에서 빼는 일은 어떤 프로젝트 조건보다 먼저 일어나야 하므로 그 자리가 필요하지만, 능력이 켜졌는지 판정하는 것은 그 필터의 일이 아니다.
임포트 필터는 마스터 스위치를 판정하지 않고 프레임워크 자동설정을 후보 집합에서 제거하는 역할만 맡는다. 이 제거는 프로젝트 조건을 평가하기 전에 실행되어야 하지만, 능력 활성 여부는 루트 자동설정이 판정한다.
## 영향
@@ -20,7 +20,7 @@ verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지
## 규칙
1. 애너테이션은 후보를 만들 뿐이
1. 애너테이션은 빈 후보만 등록한
Component 나 Repository 나 Bean 은 이 클래스가 빈이 될 수 있다는 뜻이지 빈이라는 뜻이 아니다. 스캔 범위 밖이거나 조건이 거짓이거나 소유자가 없으면 후보로 끝난다.
2. 이름은 아무것도 보장하지 않는다
@@ -32,8 +32,8 @@ verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지
4. 확인은 도달 경로로 한다
세 경로 중 어느 것이 이 클래스를 소유하는지 묻는다. 스캔이면 범위와 제외를, 자동설정이면 imports 파일과 조건을, 명시 조립이면 그 생성 지점을 확인한다.
5. main 참조 0 은 강한 신호
프로덕션 소스에서 그 타입을 참조하는 파일이 자기 자신뿐이면, 테스트만 그것을 쓴다는 뜻이다.
5. searched direct reference 0부터 확인하되 거기서 멈추지 않는
프로덕션 소스의 직접 참조 검색에서 선언 자신 외의 사용처를 찾지 못했다는 뜻까지가 증거다. 이것만으로 runtime 미사용을 확정하지 않는다. component scan, auto-configuration imports와 `@Bean`, lifecycle callback, event/post processor, ServiceLoader·reflection·configuration/resource discovery처럼 정적 직접 참조에 잡히지 않는 경로도 확인한다.
## 적용 조건
@@ -12,7 +12,7 @@ verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지
# @ConditionalOnBean은 조건이 만족될 수 있는지까지 확인해야 한다
조건부 빈을 선언해 두고 그 조건을 만족시킬 수 있는 경로가 있는지 확인하지 않아, 능력 전체가 조용히 없는 상태를 막는다. Spring 은 조건 불만족을 정상 동작으로 보므로 로그에도 액추에이터에도 신호가 남지 않는다.
조건부 빈을 선언해 두고 그 조건을 만족시킬 수 있는 경로가 있는지 확인하지 않아 능력 전체가 조립되지 않는 상태를 막는다. 조건 불만족이 application failure나 health failure로 자동 승격되지 않을 수는 있지만, condition evaluation evidence 자체가 사라지는 것은 아니다. Spring Boot의 `ConditionEvaluationReport`와, endpoint가 노출된 경우 Actuator `/actuator/conditions`에서 match 여부와 이유를 확인할 수 있다.
## 목적
@@ -29,8 +29,8 @@ verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지
3. 플랫폼이 제공하지 않겠다고 선언한 경우 질문을 바꾼다
애플리케이션이 제공해야 하는 계약이라면, 물을 것은 조건이 아니라 출하 애플리케이션이 그 계약을 이행하는가다.
4. 조건 불만족은 오류로 보고되지 않는
Spring 은 조건부 빈이 조건을 만족하지 못하는 것을 정상 동작으로 본다. 로그에도 액추에이터에도 신호가 없다.
4. 조건 불만족과 관측 가능성을 구분한
조건이 맞지 않았다는 사실이 application failure나 health failure로 자동 승격되지 않을 수 있다. 그렇다고 condition evaluation evidence가 없어지는 것은 아니다. `ConditionEvaluationReport`와, endpoint가 노출된 경우 `/actuator/conditions`에서 positive/negative match와 이유를 확인한다.
5. 꺼진 것과 조립될 수 없는 것을 구별할 방법을 남긴다
둘이 런타임에서 같아 보이면 운영자는 차이를 알 수 없다.
@@ -1,7 +1,7 @@
---
kind: REFERENCE
slug: the-startup-validator-follows-the-autoconfiguration-root
title: 시작 검증기가 도는지는 그 능력에 자동설정 루트가 있는지와 일치한다
title: 시작 검증기는 실제 lifecycle과 assembly 경로에서 실행 여부를 확인한다
topic: assembly-ownership
project: clean-architecture-backend-template
status: 게시 전
@@ -10,7 +10,7 @@ rootTreeNode: reference:the-startup-validator-follows-the-autoconfiguration-root
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
---
# 시작 검증기가 도는지는 그 능력에 자동설정 루트가 있는지와 일치한다
# 시작 검증기는 실제 lifecycle과 assembly 경로에서 실행 여부를 확인한다
검증기 파일이 존재하고 그 테스트가 통과한다는 사실을 검증이 실행된다는 증거로 읽는 것을 막는다. 규칙이 많고 잘 테스트되어 있다는 것은 품질의 증거이지 실행의 증거가 아니다.
@@ -20,11 +20,11 @@ verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지
## 규칙
1. 검증기의 호출자를 센
프로덕션 호출자가 0 이고 테스트 호출자만 있으면 그 검증은 실행 시점에 적용되지 않는다.
1. searched direct caller를 확인한
프로덕션 직접 호출을 찾지 못한 것은 강한 신호지만 그것만으로 runtime 미실행을 확정하지 않는다.
2. 자동설정 루트가 부르는지 확인한다
능력의 조립 지점이 검증기를 호출하지 않으면, 그 능력이 배포되기 시작해도 검증은 자동으로 시작되지 않는다.
2. assembly와 lifecycle 경로를 함께 확인한다
`@Bean`·component scan·auto-configuration, lifecycle callback, application event, post processor, framework discovery를 차례로 확인한다. 실제 부팅이 가능하면 condition report와 시작 로그도 함께 본다.
3. 규칙 수와 실행 여부를 분리해서 본다
규칙이 많고 잘 테스트되어 있다는 것은 품질의 증거이지 실행의 증거가 아니다.
@@ -44,7 +44,7 @@ verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지
## 예시
gRPC 플랫폼의 시작 검증기는 188줄에 13개 위반 규칙을 담고 있고, 호출자는 자기 테스트뿐이다. 같은 패키지의 자동설정은 106줄에 Bean 이 9개인데 검증기를 부르지 않는다.
gRPC 플랫폼의 시작 검증기는 188줄에 13개 위반 규칙을 담고 있다. 현재 분석에서는 searched direct caller가 테스트에 있고 같은 패키지의 자동설정이 검증기를 직접 부르지 않는다는 점을 확인했다. 이 사실을 runtime 미실행으로 확정하려면 다른 lifecycle·framework discovery 경로도 닫아야 한다.
## 관계