docs(clean-architecture-backend-template): 제1부가 채택한 것만 글감으로 남기고 다시 고른다

글감 1,001개 중 제1부(§3~§11) 앵커를 하나라도 가진 것은 112개뿐이었다. 나머지 889개는
제2부 모듈 분석 65편의 절 제목에서 나온 것이고, 그것이 재판정이 필요했던 이유다.

  주제      44 → 16   (43개가 독자 질문 없이 있었다. 지금은 전부 있다)
  글감   1,001 → 123  (제1부 앵커 112 + 제1부가 채택했는데 비어 있던 자리 11)
  후보      965 → 1,088 · PENDING 905 → 0
  error   3,042 → 0

내려온 889개는 후보 대장에 KEEP_IN_SSOT 로 남는다 — 버린 것이 아니라 분석에 남기고 독립
기록으로 만들지 않기로 한 것이다. 그 글감을 받치던 기록 파일 828개는 지웠다. 계약이 정본이고,
파일이 남아 있다는 이유로 계약에서 뺀 주제가 되살아나면 안 된다. 이력에는 그대로 있다 —
git checkout a0ca2bb -- <경로>.

제1부가 채택했는데 글감이 없던 자리 열하나를 채웠다: mongo high-water mark 가 재전달 이벤트를
삼킨 P1, admin plane 이 가드만 켜고 서비스는 켜지 않은 것과 그 짝인 결정, 실패 어휘 세 층과
SQLState 매트릭스 병합 규칙, 부하 아래에서만 새는 admission 경계, 발행 증거와 완료 판정의
분리, keyset·JSONB 결정 둘.

Concept 17개에 basis-version 을 채우고, 계약 제목과 기록 제목이 갈라져 있던 23건을 기록 쪽에
맞췄다. candidateScope 에 excludedAnchorPattern 을 적어 제2부 앵커만 가진 글감이 다시 올라올
수 없게 한다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-09-07 15:02:25 +09:00
co-authored by Claude Opus 5
parent a0ca2bb72a
commit 1f04117bbf
851 changed files with 5498 additions and 90638 deletions
@@ -1,114 +0,0 @@
---
kind: CONCEPT
slug: when-conditions-are-evaluated
title: 조건부 빈의 평가 시점 — 파싱 시점과 등록 시점
topic: assembly-ownership
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: concept:when-conditions-are-evaluated
evidenceCapturedOn: 2026-09-01
assets:
- key: when-conditions-are-evaluated
file: ../../../final/evidence/rendered/when-conditions-are-evaluated.svg
evidence:
- ../../../final/evidence/raw/when-conditions-are-evaluated.txt
source:
- 원본 분석 절은 final/document.md#a05 §14.4 이다.
---
# 조건부 빈의 평가 시점 — 파싱 시점과 등록 시점
같은 `@ConditionalOnBean`이라도 그 클래스가 자동설정으로 등록되는지 `@Import`로 들어오는지에 따라 평가 시점이 다르다. 그 차이가 조건을 항상 거짓으로 만들 수 있다.
## 관계
- **ConditionalOnBean(DataSource)이 클래스 파싱 시점에 평가되어 여덟 빈이 사라졌다**
이 개념이 실제로 문제가 된 사례다.
- **ConditionalOnBean은 조건이 만족될 수 있는지까지 확인해야 한다**
이 개념에서 나온 확인 규칙이다.
- **ConditionalOnBean 사슬의 실제 평가 순서를 확인하지 않았다**
현재 리비전에 대한 미해결 질문이다.
## 본문
<!-- body:start -->
`@ConditionalOnBean`은 그 클래스가 **언제 평가되는가**에 따라 답이 달라진다. `@AutoConfiguration`으로 등록되면 다른 자동설정 이후에 평가되지만, plain `@Configuration`이 `@Import`로 들어오면 **클래스가 파싱되는 동안 — 대상 빈 정의가 존재하기 전에** 평가된다.
## 등록 방식이 평가 시점을 정한다
:::evidence key="when-conditions-are-evaluated" alt="분석 문서 final/document.md#a05 에서 이 기록의 근거 절을 그대로 잘라낸 16줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="final/document.md#a05 발췌 — 16줄" zoom="true"
:::
## 이 저장소가 그 함정을 밟았다
여덟 빈이 조용히 사라졌다.
## 남은 회피 방법 둘
검증기를 주입받지 않고 직접 생성하기, 그리고 조건을 루트로 올리기.
:::note
현재 리비전의 각 조건부 빈이 어느 시점에 평가되는지는 ConditionEvaluationReport로 확인하지 않았다
:::
## 두 시점
`@ConditionalOnBean`은 "이 타입의 빈 정의가 이미 등록되어 있는가"를 묻는다. 그 질문의 답은 언제 묻느냐에 달라진다.
| 클래스가 들어오는 경로 | 조건이 평가되는 시점 |
|---|---|
| 자동설정 (`.imports`) | 자동설정 순서에 따라 등록 단계에서 |
| `@Import` | 그것을 import 하는 클래스가 파싱될 때 |
| 컴포넌트 스캔 | 스캔 단계에서 |
`@Import`로 들어오는 클래스가 문제다. 부모 설정이 파싱되는 시점에 자식 클래스의 클래스 수준 조건이 함께 평가되는데, 그 시점에는 자동설정이 만들 빈 정의가 아직 존재하지 않는다.
## 이 저장소가 겪은 형태
```java
/**
* <p>This class used to carry {@code @ConditionalOnBean(DataSource.class)}. It is imported by
* {@code PersistenceJpaRootAutoConfiguration} rather than auto-configured, so that condition was
* evaluated while the class was parsed — before the datasource bean definition existed — and was
* therefore false in every real deployment. All eight beans below silently disappeared, and nothing
* reported it because nothing depended on any of them. It surfaced only when the datasource
* validator was finally wired to a caller and the JPA Compose lane answered "No qualifying bean".
*/
```
두 문장이 이 개념의 핵심이다. 조건이 모든 실제 배포에서 거짓이었다는 것, 그리고 아무것도 그것을 보고하지 않았다는 것.
보고되지 않은 이유가 특히 중요하다. 사라진 여덟 빈에 의존하는 것이 없었기 때문이다. 의존이 있었다면 미충족 의존성으로 시끄럽게 실패했을 것이다.
## 해결 방향은 순서가 아니라 제거였다
```java
/**
* <p>The condition is removed rather than reordered: this class is reached only through the JPA
* root, which already carries the master switch, so "is there a datasource" has been answered yes
* by the time it is parsed. A bean here that needs one takes it as a parameter, and a missing
* datasource with the switch on is then a loud failure — which is the outcome that was wanted,
* rather than the layer vanishing.
*/
```
:::tip
조건을 옮기거나 순서를 바꾸는 대신, 그 조건이 이미 답해진 지점으로 도달 경로를 제한하고 조건 자체를 없앴다. 그리고 필요한 의존은 파라미터로 받게 해서, 없을 때 조용히 사라지는 대신 시끄럽게 실패하게 만들었다.
:::
## 확인 방법
정적으로는 두 가지를 본다.
1. 그 클래스가 `.imports`에 있는가, 아니면 다른 클래스가 `@Import` 하는가
2. 조건이 요구하는 빈을 누가 언제 등록하는가
런타임으로는 `debug=true` 부팅의 조건 평가 리포트가 답한다. `Negative matches` 항목의 사유 문자열이 "빈 정의 없음"인지 "타입 자체가 없음"인지를 구별해 준다.
<!-- body:end -->