docs(keycloak-session-store): import the session-storage lab as a new project
The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
43bccd08a8
commit
b2963105a8
+141
@@ -0,0 +1,141 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: a05-f019-ssot
|
||||
title: 정본이 어디인지 주석에 적어 두고 정의는 다시 타이핑한다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:a05-f019-ssot
|
||||
evidenceCapturedOn: 2026-09-02
|
||||
assets:
|
||||
- key: a05-f019-ssot
|
||||
file: ../../../final/evidence/rendered/a05-f019-ssot.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/a05-f019-ssot.txt
|
||||
source:
|
||||
- 원본 분석 절은 `analysis/05-adapter-outbound-persistence-jpa.md` §51 이다. 두 목록의 이름과 차이가 `postgresql` 과 `h2` 라는 것이 §51.1 과 §51.2 에, 리프 목록이 바깥 소비자를 스캔하지 않는다는 것이 §51.3 에 있다. 같은 문서 §55 의 backlog 가 이것을 P2/P3 아키텍처 거버넌스 강화로 분류한다.
|
||||
- 사본의 javadoc 이 정본이 어디인지 적어 두고도 그 값을 코드로 읽지 않는다는 것은 여기서 확인했다.
|
||||
---
|
||||
|
||||
# 정본이 어디인지 주석에 적어 두고 정의는 다시 타이핑한다
|
||||
|
||||
리프가 내보내는 패키지 목록을 선언하고, 컴포지션 루트의 소비자 규칙이 같은 목록을 자기 안에 다시 적는다. 사본의 javadoc 은 정본이 리프 쪽이라고 이름으로 적지만, 그 이름을 코드로 읽는 곳은 없다. 두 목록은 이미 두 항목 다르다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **문서의 수치는 세지 말고 파생하거나 게이트로 붙든다**
|
||||
문서의 수치를 세는 대신 파생하거나 게이트로 붙들라는 규칙이다.
|
||||
- **빠뜨림이 통과가 되는 게이트는 게이트가 아니다**
|
||||
한쪽에 항목을 더해도 다른 쪽이 조용한 형태가 같다.
|
||||
- **같은 개념의 두 어휘가 공존하면 하나를 죽은 것으로 표시한다**
|
||||
같은 값을 두 곳이 각자 적고 있어 어느 쪽이 정본인지 정해야 한다.
|
||||
|
||||
## 문제
|
||||
|
||||
이 리프에는 jar 하나에 공개 구현 타입이 많이 들어 있다. 그래서 자바 접근 제어와 아키텍처 노출 목록을 각각 따로 둔다.
|
||||
|
||||
내보낼 패키지 목록을 두 곳이 각각 들고 있다.
|
||||
|
||||
## 결론
|
||||
|
||||
리프의 경계 시험 쪽에는 열 개가 선언돼 있다. springdata 와 querydsl 은 거기 없다.
|
||||
|
||||
컴포지션 루트의 아키텍처 시험이 같은 열 개를 자기 안에 다시 적고, 벤더 진입점 둘을 더 넣는다. postgresql 과 h2 다. 이유는 그 자리 주석에 적혀 있다. 벤더 설정이 코어 JPA 설정을 임포트하는 방향이라 컴포지션 루트가 대신 막을 단일 내부 진입점이 없고, 방향을 뒤집으면 패키지 순환이 생겼다는 것이다.
|
||||
|
||||
사본의 javadoc 에는 이 목록이 리프의 export 허용 목록을 옮겨 적은 것이고 정의는 리프의 경계 시험에 있다고 적혀 있다.
|
||||
|
||||
그 문장이 컴포지션 루트에서 그 클래스를 언급하는 유일한 줄이다. 선언 파일 밖에서 그 목록 상수를 참조하는 자바 코드는 저장소 전체에 0 이다. 어느 쪽이 정본인지는 산문이 말하고, 값은 사람이 옮겨 적는다.
|
||||
|
||||
리프 목록은 바깥 소비자를 검사하지도 않는다. 그 목록을 쓰는 시험 둘은 목록에 적힌 패키지가 실제로 있는지, 그 패키지가 카탈로그의 거버넌스 대상인지를 본다. 임포트 관계는 컴포지션 루트 쪽 규칙이 따로 본다.
|
||||
|
||||
그래서 두 시험 모두 통과한다. 통과는 각자의 규칙을 만족한다는 뜻이고, 두 목록이 같다는 뜻은 아니다. 지금 이미 두 항목 다르다.
|
||||
|
||||
권고는 등록부를 한 곳에 두고 두 검사가 같은 데이터를 보게 하라는 것이다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12
|
||||
확인 방식 : 두 목록의 항목 추출과 집합 대조, 정본 참조 계수, 리프 시험의 검사 범위 확인
|
||||
소스 수정 : x
|
||||
실행 : 없음. 정적 검색과 집합 연산이다.
|
||||
|
||||
## 재현 조건
|
||||
|
||||
1. 리프 경계 시험의 export 목록을 읽는다.
|
||||
2. 컴포지션 루트의 소비자 규칙 안에 있는 같은 이름의 목록을 읽는다.
|
||||
3. 두 집합을 뽑아 차집합을 구한다.
|
||||
4. 사본의 javadoc 이 정본을 어떻게 지목하는지 읽는다.
|
||||
5. 컴포지션 루트에서 그 정본 클래스를 언급하는 줄과, 선언 파일 밖에서 그 상수를 참조하는 코드를 각각 센다.
|
||||
6. 리프 목록을 쓰는 시험 둘이 무엇을 확인하는지 읽는다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
이 리프는 하나의 jar 안에 공개 구현 타입이 많다. 그래서 자바의 `public` 과 아키텍처가 내보내는 패키지를 따로 관리한다.
|
||||
|
||||
내보내는 패키지 목록이 두 곳에 있다.
|
||||
|
||||
## 두 목록과 그 차이
|
||||
|
||||
:::evidence key="a05-f019-ssot" alt="리프 경계 시험이 선언하는 export 패키지 목록, 컴포지션 루트의 소비자 규칙 안에 다시 적힌 같은 목록과 거기 더해진 벤더 진입점 둘과 그 이유 주석, 두 집합의 크기와 차집합, 사본의 javadoc 이 정본을 지목하는 줄과 그 정본 클래스를 언급하는 줄 수와 선언 파일 밖의 상수 참조 수, 그리고 리프 목록을 쓰는 두 시험이 무엇을 보는지를 출력한 터미널 기록." caption="리프 10개와 루트 12개, 차이는 postgresql 과 h2 · 사본 javadoc 이 정본을 어디라고 적는지 · 그 클래스 언급 1줄은 그 주석뿐 · 상수 참조 0 · 리프 시험 둘은 목록의 자기 정합만 확인 — 65줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
리프의 경계 시험이 열 개를 선언한다.
|
||||
|
||||
```java
|
||||
private static final Set<String> EXPORTED_PACKAGES =
|
||||
Set.of(
|
||||
"api", "notification.configuration", "transaction", "security",
|
||||
"observation", "migration", "hibernate", "fileserver", "failure", "config");
|
||||
```
|
||||
|
||||
컴포지션 루트의 아키텍처 시험은 같은 열 개에 둘을 더 적는다. 그 자리의 주석이 이유를 적는다.
|
||||
|
||||
```text
|
||||
The two vendor entry points. A vendor configuration imports the core JPA config rather
|
||||
than the reverse, so there is no single internal entry the composition root could gate
|
||||
instead — inverting the import to make one produced a package cycle.
|
||||
```
|
||||
|
||||
집합으로 빼면 차이가 정확히 둘이다.
|
||||
|
||||
```text
|
||||
리프 10개 / 루트 12개
|
||||
루트에만 있는 것: ['h2', 'postgresql']
|
||||
리프에만 있는 것: []
|
||||
```
|
||||
|
||||
## 사본이 정본을 지목하는 방식
|
||||
|
||||
```text
|
||||
The list is the leaf's export allowlist, mirrored here because this is the consumer side
|
||||
of the same boundary. JpaModuleBoundaryTest owns the definition.
|
||||
```
|
||||
|
||||
그 문장이 컴포지션 루트에서 리프 경계 시험을 언급하는 유일한 줄이다. 선언 파일 밖에서 `EXPORTED_PACKAGES` 를 참조하는 자바 코드는 저장소 전체에 0 이다.
|
||||
|
||||
정본을 지목하는 것은 산문이고, 값은 손으로 옮겨져 있다.
|
||||
|
||||
## 리프 목록은 바깥 소비자를 보지 않는다
|
||||
|
||||
그 목록을 쓰는 시험은 둘이다. 목록에 적힌 패키지가 소스 트리에 실제로 있는지, 그리고 그 패키지가 카탈로그의 거버넌스 대상인지를 본다.
|
||||
|
||||
누가 무엇을 임포트하는지는 컴포지션 루트의 규칙이 따로 본다. split 은 실수가 아니라 역할 분리의 결과이고, 그래서 어느 쪽도 상대를 검사할 이유가 없다.
|
||||
|
||||
## 두 시험이 각각 무엇을 보는가
|
||||
|
||||
두 시험의 입력이 겹치지 않는다. 리프 시험은 리프의 소스 트리와 자기 카탈로그만, 루트 시험은 루트의 임포트 그래프와 자기 목록만 읽는다.
|
||||
|
||||
한쪽 목록이 늘어도 다른 쪽 단언의 입력은 그대로다. 실패할 근거가 없다.
|
||||
|
||||
## 고칠 방향
|
||||
|
||||
내보내는 패키지 등록부를 한 곳으로 옮기고, 리프의 패키지 그래프 검사와 소비자 규칙이 같은 데이터를 읽게 한다. 분석 문서의 권고가 그것이다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
한쪽 목록에 항목을 더해 다른 쪽이 조용한지 실행으로 확인하지 않았다. 두 선언을 대조하고 참조를 센 것까지가 확인 범위다.
|
||||
|
||||
<!-- body:end -->
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: a06-f016-flamingock
|
||||
title: javadoc은 재개 가능한 것만 거부한다고 적는데 검사는 마이그레이션을 보지 않는다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:a06-f016-flamingock
|
||||
evidenceCapturedOn: 2026-09-02
|
||||
assets:
|
||||
- key: a06-f016-flamingock
|
||||
file: ../../../final/evidence/rendered/a06-f016-flamingock.svg
|
||||
- key: a06-f016-flamingock-probe
|
||||
file: ../../../final/evidence/rendered/a06-f016-flamingock-probe.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/a06-f016-flamingock.txt
|
||||
- ../../../final/evidence/raw/a06-f016-flamingock-probe.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/06-adapter-outbound-persistence-mongo.md#L942 이다. 등급은 P3 이다. 그 절이 담은 것은 넷이다. 펜스 반환값과 그 근거, javadoc 마지막 문장의 범위, 82행 검사가 적용 스트림보다 앞이라는 위치, 그리고 시험이 이 조합을 다루지 않는다는 것.
|
||||
- 빈 목록도 같은 문장으로 거절된다는 것, 같은 마이그레이션이 펜스가 있는 리스에서는 완료된다는 것, 154행이 재개 가능한 마이그레이션에만 걸리는 펜스 조건이라는 것, 그리고 검사를 옮겨도 157행에서 다시 막힌다는 것은 이 기록에서 확인했다.
|
||||
---
|
||||
|
||||
# javadoc은 재개 가능한 것만 거부한다고 적는데 검사는 마이그레이션을 보지 않는다
|
||||
|
||||
잠금 어댑터는 펜스로 미지정 값을 돌려주고 그 근거를 javadoc 에 적는다. 이어지는 한 문장이 실행자의 거부 범위를 재개 가능한 마이그레이션으로 한정한다. 실제 조건은 리스의 펜스 값 하나여서 마이그레이션이 하나도 없어도 거부한다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **fenced lease — 만료 시각만으로는 부족한 이유**
|
||||
펜싱이 무엇을 보장하는지 다룬 개념이다.
|
||||
- **recordApplied가 펜스를 비교하지 않아 밀려난 실행자가 원장을 차지한다**
|
||||
같은 원장의 펜스 처리 사례다.
|
||||
- **문서가 UUIDv7이라 말하고 생성되는 것은 v4다**
|
||||
두 사례 모두 javadoc 이 적은 것과 코드가 만드는 값이 다르다.
|
||||
|
||||
## 문제
|
||||
|
||||
펜스 메서드가 미지정 값을 돌려주는 것과 그 근거는 옳다. 로컬 카운터로 펜싱을 흉내내면 펜싱처럼 보이면서 아무것도 보호하지 않고, 프로세스마다 따로 세는 수는 다른 프로세스가 무엇을 획득했는지에 대해 아무 말도 하지 않는다.
|
||||
|
||||
어긋난 것은 같은 javadoc 의 마지막 한 문장이다. 실행자가 바로 이 이유로 재개 가능한 마이그레이션을 거부한다고 적는다.
|
||||
|
||||
## 결론
|
||||
|
||||
그 문장이 가리키는 장치는 실재한다. 재개 가능한 마이그레이션만 체크포인트를 남기고, 그 저장에 펜스가 넘어가며, 원장은 미지정 펜스로 들어온 체크포인트를 거절한다. 그 자리를 지키는 조건은 재개 가능성 하나만 본다.
|
||||
|
||||
닿지 못할 뿐이다. 진입점의 세 관문 중 세 번째가 리스의 펜스 값만 보고 던지고, 그 자리는 적용 스트림보다 앞이다. 조건에 마이그레이션이 들어가지 않으므로 목록이 비어 있어도 같은 문장이 나온다.
|
||||
|
||||
탐침으로 확인했다. 빈 목록을 이 리스로 적용해도 거절된다. 목록에 아무것도 없어 재개 가능한지 여부가 존재하지 않는데도 같다. 체크포인트를 만들지 않는 마이그레이션 한 건도 마찬가지이고, 그 한 건은 펜스가 있는 리스에서는 완료로 끝난다.
|
||||
|
||||
거절 문장 자체는 정확하다. 펜스 없는 리스는 멈춘 실행자를 배제할 수 없다고 말하고 재개 가능성을 언급하지 않는다. 그리고 거절 자체가 안전한 선택이다. 문제는 javadoc 을 읽고 이 어댑터를 고른 쪽이 재개 불가능한 마이그레이션은 돈다고 읽는다는 것이다.
|
||||
|
||||
수정은 사실상 하나다. javadoc 을 실제 범위로 고치는 것이다. 검사를 스트림 안으로 옮기는 쪽은 둘에 막힌다. 재개 가능성은 실행이 체크포인트를 돌려준 뒤에야 드러나므로 실행 전에 판별할 수 없고, 옮기더라도 적용이 끝난 뒤 같은 미지정 값이 원장 기록으로 넘어가 거기서 거절된다. 거절 시점이 실행 전에서 실행 후로 밀릴 뿐이다.
|
||||
|
||||
지금 배포를 멈추는 결함은 아니다. 실행자와 원장과 잠금을 프로덕션에서 참조하는 곳이 없고, 이 어댑터를 만드는 곳은 시험 두 곳이며 그 시험들은 적용을 부르지 않는다. 이 리프를 가져다 조립하는 쪽이 처음 실행할 때 드러난다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12
|
||||
확인 방식 : 밀폐된 탐침으로 실행자 진입점 호출
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
1. 잠금 어댑터의 펜스 메서드가 돌려주는 값과 그 javadoc 을 읽는다.
|
||||
2. 진입점이 지나는 세 관문과 적용 스트림의 줄 번호를 확인한다.
|
||||
3. 그 파일에서 리스의 펜스를 읽는 곳을 전수로 세고, 각각이 그 값을 어디로 넘기는지 확인한다.
|
||||
4. 원장이 미지정 펜스를 어떻게 다루는지 읽는다.
|
||||
5. 마이그레이션 인터페이스에 재개 가능성을 선언하는 멤버가 있는지 확인한다.
|
||||
6. 어댑터로 빈 목록을 적용한다.
|
||||
7. 체크포인트를 만들지 않는 마이그레이션 한 건을 같은 리스로, 그리고 펜스가 있는 리스로 각각 적용한다.
|
||||
8. 어댑터가 나오는 곳과 프로덕션 참조를 전수로 센다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
잠금 어댑터의 펜스 메서드는 미지정 값을 돌려주고, javadoc 이 그 선택의 근거를 적는다.
|
||||
|
||||
## 근거 문단과 그 뒤의 한 문장
|
||||
|
||||
:::evidence key="a06-f016-flamingock" alt="잠금 어댑터의 펜스 메서드와 그 javadoc, 실행자 진입점이 지나는 세 관문, 그 파일에서 리스의 펜스를 읽는 곳 전수와 각각의 줄 번호, 원장이 미지정 펜스를 거절하는 자리, 마이그레이션 인터페이스의 멤버 목록, 이 어댑터가 나오는 곳 전수, 실행자와 원장과 잠금의 프로덕션 참조, 그리고 마이그레이션을 적용하는 진입점 전수를 출력한 터미널 기록." caption="펜스는 UNFENCED 를 반환하고 javadoc 은 재개 가능한 것을 거부한다고 적음 · 관문은 보유 검사 74행, 목록 검증 80행, 펜스 검사 82행이고 적용 스트림은 93행 · 펜스를 읽는 곳은 82·154·157행이고 뒤 둘은 원장으로 넘어가 미지정이면 거절 · 인터페이스에 재개 가능성 멤버 없음 · 프로덕션 참조 0 — 73줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
```java
|
||||
/**
|
||||
* Unfenced: the engine's lock exposes no monotonic token.
|
||||
*
|
||||
* <p>Reported honestly rather than synthesised from a local counter, which would look like
|
||||
* fencing and protect nothing — a per-process counter says nothing about what another process
|
||||
* acquired. The runner refuses resumable migrations under an unfenced lease for exactly this
|
||||
* reason.
|
||||
*/
|
||||
```
|
||||
|
||||
앞 두 문장은 왜 흉내내지 않았는지를 적는다. 문제는 마지막 문장이 거절 범위를 재개 가능한 마이그레이션으로 한정한 것이다.
|
||||
|
||||
## 그 문장이 가리키는 장치는 실재한다
|
||||
|
||||
실행자가 리스의 펜스를 읽는 곳은 셋이다.
|
||||
|
||||
```text
|
||||
82: if (lock.fence() == MongoMigrationLock.UNFENCED) {
|
||||
154: result.resumePoint().ifPresent(checkpoint -> ledger.saveCheckpoint(checkpoint, lock.fence()));
|
||||
157: migration.id(), migration.checksum(), context.operator(), clock.instant(), lock.fence());
|
||||
```
|
||||
|
||||
154행은 재개 지점을 남긴 마이그레이션에만 걸린다. 그 값을 받은 원장은 미지정이면 거절한다.
|
||||
|
||||
```java
|
||||
private static void requireCurrentFence(long fence, String what) {
|
||||
if (fence == MongoMigrationLock.UNFENCED) {
|
||||
```
|
||||
|
||||
재개 가능성에만 반응하는 펜스 조건이 그 자리에 있다. javadoc 의 마지막 문장은 근거 없는 착오가 아니다.
|
||||
|
||||
## 세 번째 관문이 먼저 던진다
|
||||
|
||||
```text
|
||||
74: if (!lock.held()) {
|
||||
80: migrations.forEach(this::validate);
|
||||
82: if (lock.fence() == MongoMigrationLock.UNFENCED) {
|
||||
93: return migrations.stream()
|
||||
```
|
||||
|
||||
82행의 조건은 리스의 펜스 값 하나다. 80행은 목록을 순회하지만 원장과 체크섬을 볼 뿐이고, 82행은 그 결과도 마이그레이션도 참조하지 않는다. 154행에 닿으려면 93행의 스트림에 들어가야 하는데 82행이 그보다 앞이다.
|
||||
|
||||
그 자리에서 나오는 문장도 재개 가능성을 말하지 않는다.
|
||||
|
||||
```java
|
||||
"this migration lease exposes no fencing token, so a stalled runner cannot be excluded;"
|
||||
+ " use a fenced lease or run the migration inside a maintenance window with the"
|
||||
+ " application stopped"
|
||||
```
|
||||
|
||||
## 빈 목록도 거절된다
|
||||
|
||||
:::evidence key="a06-f016-flamingock-probe" alt="잠금 어댑터가 보고하는 보유 여부와 펜스 값, 빈 마이그레이션 목록과 체크포인트를 만들지 않는 마이그레이션 한 건을 그 리스로 적용한 결과와 거절 문장, 그리고 같은 마이그레이션을 펜스가 있는 리스로 적용한 결과를 출력한 터미널 기록." caption="펜스는 -1 · 빈 목록도 거절, 재개 불가 한 건도 같은 문장으로 거절 · 같은 마이그레이션이 펜스가 있는 리스에서는 COMPLETED — 11줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
이 어댑터로 리스를 만들고 아무것도 없는 목록을 적용했다.
|
||||
|
||||
```text
|
||||
[검사가 마이그레이션을 보기 전에 걸린다]
|
||||
마이그레이션 0건 -> 거절, MongoOperationRejectedException
|
||||
this migration lease exposes no fencing token, so a stalled runner cannot be excluded; ...
|
||||
재개 불가 1건 -> 거절, MongoOperationRejectedException
|
||||
this migration lease exposes no fencing token, so a stalled runner cannot be excluded; ...
|
||||
```
|
||||
|
||||
목록이 비어 있으면 재개 가능한지 여부가 존재하지 않는데 같은 문장이 나온다. 한 건을 넣어도 결과는 같다. 그 한 건은 한 번의 호출로 완료를 돌려주므로 체크포인트를 남기지 않는다.
|
||||
|
||||
같은 마이그레이션을 펜스가 있는 리스로 적용하면 이렇게 된다.
|
||||
|
||||
```text
|
||||
[같은 마이그레이션, 펜스가 있는 리스]
|
||||
재개 불가 1건 -> 실행, 결과 1건 COMPLETED
|
||||
```
|
||||
|
||||
거절의 대상은 마이그레이션이 아니라 리스다.
|
||||
|
||||
## 검사를 옮기는 수정은 두 곳에 막힌다
|
||||
|
||||
마이그레이션 인터페이스에는 재개 가능성을 선언하는 멤버가 없다.
|
||||
|
||||
```text
|
||||
17: MongoMigrationId id();
|
||||
20: MongoMigrationChecksum checksum();
|
||||
23: MongoMigrationPrecondition precondition();
|
||||
26: MongoMigrationResult execute(MongoMigrationContext context);
|
||||
29: MongoMigrationPostcondition postcondition();
|
||||
```
|
||||
|
||||
재개 가능한지는 `execute` 가 체크포인트를 돌려준 뒤에야 드러난다. 그리고 82행을 스트림 안으로 옮겨도 157행이 같은 미지정 값을 원장 기록으로 넘긴다. 원장은 그것을 거절하므로, 데이터베이스는 바뀌고 원장에는 아무것도 남지 않는 상태가 된다.
|
||||
|
||||
## 이 어댑터가 나오는 곳
|
||||
|
||||
어댑터를 만드는 곳은 시험 두 곳이다. 하나는 획득 실패를, 다른 하나는 해제와 해제 뒤 갱신 거절을 확인한다. 적용을 부르는 시험은 없어서 javadoc 과 82행의 어긋남이 시험에도 걸리지 않는다.
|
||||
|
||||
조립 쪽도 같다. 실행자와 원장과 잠금을 프로덕션에서 참조하는 곳이 없고, 자동 구성이 이 패키지에서 만드는 빈도 없다. 지금 도는 배포를 멈추는 결함이 아니라, 이 리프를 가져다 조립하는 쪽이 처음 실행하는 순간에 드러날 결함이다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
이 어댑터를 실제 배포에서 골랐을 때의 운영 경험은 다루지 않았다. 확인한 것은 세 번째 관문의 조건과 그 조건이 만드는 거절의 범위다.
|
||||
|
||||
<!-- body:end -->
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: grpc-advanced-bootstrap-f02
|
||||
title: 승격 게이트가 하향 전이도 승격 규칙으로 판정하고, javadoc 이 약속한 거부는 없다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:grpc-advanced-bootstrap-f02
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: grpc-advanced-bootstrap-f02
|
||||
file: ../../../final/evidence/rendered/grpc-advanced-bootstrap-f02.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/grpc-advanced-bootstrap-f02.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/grpc/grpc-advanced-bootstrap.md#L182 이다.
|
||||
module: grpc-advanced-bootstrap
|
||||
priority: P3
|
||||
---
|
||||
|
||||
# 승격 게이트가 하향 전이도 승격 규칙으로 판정하고, javadoc 이 약속한 거부는 없다
|
||||
|
||||
던지는 경우는 널과 from == to 둘뿐이다. "이 게이트가 다루는 전이가 아닐 때" 라는 조건에 해당하는 검사가 없다.
|
||||
|
||||
## 문제
|
||||
|
||||
던지는 경우는 널과 from == to 둘뿐이다.
|
||||
|
||||
"이 게이트가 다루는 전이가 아닐 때" 라는 조건에 해당하는 검사가 없다.
|
||||
|
||||
## 결론
|
||||
|
||||
그래서 하향 전이가 승격 규칙으로 판정된다.
|
||||
|
||||
능력을 철회하려는 결정이 증거 부족을 이유로 막힌다.
|
||||
|
||||
방향이 뒤집혀 있다.
|
||||
|
||||
지금은 도달성이 낮다 — 이 게이트를 부르는 production 코드가 없고 테스트도 상향 전이만 넣는다.
|
||||
|
||||
기록하는 이유는 javadoc 이 그 거부를 이미 약속했다는 점이다.
|
||||
|
||||
수정은 to.ordinal() 이 아니라 등급의 서열을 명시한 뒤 상향 전이만 받고 나머지는 던지는 것이다.
|
||||
|
||||
철회는 별도 경로가 필요하다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12 java -version 으로 확인
|
||||
Gradle : 9.0.0 src/gradle/wrapper/gradle-wrapper.properties 의 distributionUrl 로 확인
|
||||
확인 방식 : 승격 게이트가 던지는 조건 전수 확인과 javadoc 이 약속한 거부의 대조
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 analysis/grpc/grpc-advanced-bootstrap.md#L182 에 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
던지는 경우는 널과 `from == to` 둘뿐이다. "이 게이트가 다루는 전이가 아닐 때" 라는 조건에 해당하는 검사가 없다.
|
||||
|
||||
## 게이트가 던지는 두 경우
|
||||
|
||||
:::evidence key="grpc-advanced-bootstrap-f02" alt="분석 문서 analysis/grpc/grpc-advanced-bootstrap.md 에서 이 기록의 근거 절을 그대로 잘라낸 15줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="analysis/grpc/grpc-advanced-bootstrap.md 발췌 — 15줄" zoom="true"
|
||||
:::
|
||||
|
||||
## 하향 전이가 승격 규칙으로 판정된다
|
||||
|
||||
능력을 철회하려는 결정이 증거 부족을 이유로 막힌다. 방향이 뒤집혀 있다.
|
||||
|
||||
## 지금은 도달성이 낮다
|
||||
|
||||
이 게이트를 부르는 production 코드가 없고 테스트도 상향 전이만 넣는다. 기록하는 이유는 javadoc 이 그 거부를 이미 약속했다는 점이다.
|
||||
|
||||
## 수정
|
||||
|
||||
`to.ordinal()` 이 아니라 등급의 서열을 명시한 뒤 상향 전이만 받고 나머지는 던지는 것이다. 철회는 별도 경로가 필요하다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
하향 전이를 실제로 넣어 게이트의 판정을 관측하지 않았다. 던지는 조건이 널과 from == to 둘뿐이라는 것으로 판정했다.
|
||||
|
||||
<!-- body:end -->
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: grpc-advanced-edition-f02
|
||||
title: 승격 차단 목록에 담금 기간과 실환경 항목이 없다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:grpc-advanced-edition-f02
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: grpc-advanced-edition-f02
|
||||
file: ../../../final/evidence/rendered/grpc-advanced-edition-f02.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/grpc-advanced-edition-f02.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/grpc/grpc-advanced-edition.md#L206 이다.
|
||||
module: grpc-advanced-edition
|
||||
priority: P3
|
||||
---
|
||||
|
||||
# 승격 차단 목록에 담금 기간과 실환경 항목이 없다
|
||||
|
||||
GrpcEdition2024Gate.promotionBlockers 가 보는 것은 셋이다 — 호환성 보고서의 문제들, 소비자 이관 계획, 승격 ADR. 같은 가족의 GrpcAdvancedPromotionGate 는 EDITION_2024 능력에 대해 일곱 증거 항목과 7일 담금을 요구한다.
|
||||
|
||||
## 문제
|
||||
|
||||
GrpcEdition2024Gate.promotionBlockers 가 보는 것은 셋이다 — 호환성 보고서의 문제들, 소비자 이관 계획, 승격 ADR.
|
||||
|
||||
같은 가족의 GrpcAdvancedPromotionGate 는 EDITION_2024 능력에 대해 일곱 증거 항목과 7일 담금을 요구한다.
|
||||
|
||||
## 결론
|
||||
|
||||
두 게이트가 같은 능력의 승격을 서로 다른 기준으로 판정한다.
|
||||
|
||||
두 게이트가 각각 다른 것을 묻는다고 볼 수도 있다 — 하나는 편집 자체의 호환성, 하나는 능력의 운영 준비도.
|
||||
|
||||
다만 어느 쪽도 상대를 부르지 않고, 문서에도 두 게이트의 관계가 적혀 있지 않다.
|
||||
|
||||
승격을 실제로 수행할 때 어느 쪽을 만족해야 하는지가 코드에서 답해지지 않는다.
|
||||
|
||||
수정은 promotionBlockers 가 GrpcAdvancedPromotionGate.evaluate 의 결과를 포함하게 하거나, 두 게이트의 역할 분담을 자바독에 적는 것이다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12 java -version 으로 확인
|
||||
Gradle : 9.0.0 src/gradle/wrapper/gradle-wrapper.properties 의 distributionUrl 로 확인
|
||||
확인 방식 : GrpcEdition2024Gate 참조 9건 검색과 GrpcAdvancedPromotionGate 의 요구 항목 대조
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 analysis/grpc/grpc-advanced-edition.md#L206 에 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
`GrpcEdition2024Gate.promotionBlockers` 가 보는 것은 셋이다 — 호환성 보고서의 문제들, 소비자 이관 계획, 승격 ADR.
|
||||
|
||||
## GrpcEdition2024Gate 참조 위치
|
||||
|
||||
:::evidence key="grpc-advanced-edition-f02" alt="코드베이스에서 GrpcEdition2024Gate 를 검색한 출력 9줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="GrpcEdition2024Gate 코드베이스 검색 — 9줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## 같은 가족의 다른 게이트는 다른 기준을 쓴다
|
||||
|
||||
`GrpcAdvancedPromotionGate` 는 `EDITION_2024` 능력에 대해 일곱 증거 항목과 7일 담금을 요구한다. 두 게이트가 같은 능력의 승격을 서로 다른 기준으로 판정한다.
|
||||
|
||||
## 각각 다른 것을 묻는다고 볼 수도 있다
|
||||
|
||||
하나는 편집 자체의 호환성, 하나는 능력의 운영 준비도다. 다만 어느 쪽도 상대를 부르지 않고, 문서에도 두 게이트의 관계가 적혀 있지 않다. 승격을 실제로 수행할 때 어느 쪽을 만족해야 하는지가 코드에서 답해지지 않는다. 수정은 `promotionBlockers` 가 `GrpcAdvancedPromotionGate.evaluate` 의 결과를 포함하게 하거나, 두 게이트의 역할 분담을 자바독에 적는 것이다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
두 게이트를 실제로 실행해 판정 차이를 관측하지 않았다. 차단 목록의 구성 요소 대조로 판정했다.
|
||||
|
||||
<!-- body:end -->
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: grpc-advanced-resilience-f01
|
||||
title: 부트스트랩 대조가 문서 어디든의 부분 문자열을 본다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:grpc-advanced-resilience-f01
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: grpc-advanced-resilience-f01
|
||||
file: ../../../final/evidence/rendered/grpc-advanced-resilience-f01.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/grpc-advanced-resilience-f01.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/grpc/grpc-advanced-resilience.md#L136 이다.
|
||||
module: grpc-advanced-resilience
|
||||
priority: P3
|
||||
---
|
||||
|
||||
# 부트스트랩 대조가 문서 어디든의 부분 문자열을 본다
|
||||
|
||||
세 검사가 모두 문서 전체에 대한 부분 문자열 포함이다. JSON 파서를 쓰지 않은 이유는 자바독이 밝힌다 — 세 필드를 보려고 파서를 xDS 를 켜는 모든 배포의 실행 클래스패스에 올리지 않겠다는 것이다.
|
||||
|
||||
## 문제
|
||||
|
||||
세 검사가 모두 문서 전체에 대한 부분 문자열 포함이다.
|
||||
|
||||
JSON 파서를 쓰지 않은 이유는 자바독이 밝힌다 — 세 필드를 보려고 파서를 xDS 를 켜는 모든 배포의 실행 클래스패스에 올리지 않겠다는 것이다.
|
||||
|
||||
## 결론
|
||||
|
||||
그 판단 자체는 이 저장소의 다른 결정들과 일관된다.
|
||||
|
||||
다만 검사의 형태가 그 판단보다 느슨하다.
|
||||
|
||||
"tls" 가 문서 어디에든 있으면 통과한다.
|
||||
|
||||
통제 평면 채널이 insecure 로 설정되어 있고 다른 곳(예: 서버 리스너 설정)에 tls 라는 낱말이 있으면 두 번째 검사가 지나간다.
|
||||
|
||||
자원 이름공간이 주석·다른 필드·다른 서버 항목에 있어도 통과한다.
|
||||
|
||||
세 번째 검사가 막으려는 것은 "이 클라이언트가 자기 이름공간 밖을 구독하는 것" 인데, 문자열이 어딘가에 있다는 것은 그것이 이 클라이언트의 구독 대상이라는 뜻이 아니다.
|
||||
|
||||
그리고 이 검사가 막으려는 실패는 자바독이 스스로 "조용하다" 고 적은 것이다 — 아무것도 오류가 되지 않는 종류다.
|
||||
|
||||
느슨한 검사와 조용한 실패의 조합이 이 항목을 기록하는 이유다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12 java -version 으로 확인
|
||||
Gradle : 9.0.0 src/gradle/wrapper/gradle-wrapper.properties 의 distributionUrl 로 확인
|
||||
확인 방식 : 세 검사가 쓰는 대조 방식과 자바독이 밝힌 파서 미사용 근거의 대조
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 analysis/grpc/grpc-advanced-resilience.md#L136 에 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
세 검사가 모두 문서 전체에 대한 부분 문자열 포함이다.
|
||||
|
||||
## 세 검사가 쓰는 대조 방식
|
||||
|
||||
:::evidence key="grpc-advanced-resilience-f01" alt="분석 문서 analysis/grpc/grpc-advanced-resilience.md 에서 이 기록의 근거 절을 그대로 잘라낸 15줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="analysis/grpc/grpc-advanced-resilience.md 발췌 — 15줄" zoom="true"
|
||||
:::
|
||||
|
||||
## 파서를 쓰지 않은 판단 자체는 일관된다
|
||||
|
||||
자바독이 밝히듯 세 필드를 보려고 파서를 xDS 를 켜는 모든 배포의 실행 클래스패스에 올리지 않겠다는 것이다. 다만 검사의 형태가 그 판단보다 느슨하다.
|
||||
|
||||
## 낱말이 어디에 있든 통과한다
|
||||
|
||||
`"tls"` 가 문서 어디에든 있으면 통과한다. 통제 평면 채널이 `insecure` 로 설정되어 있고 다른 곳(예: 서버 리스너 설정)에 `tls` 라는 낱말이 있으면 두 번째 검사가 지나간다. 자원 이름공간이 주석·다른 필드·다른 서버 항목에 있어도 통과한다. 세 번째 검사가 막으려는 것은 "이 클라이언트가 자기 이름공간 밖을 구독하는 것" 인데, 문자열이 어딘가에 있다는 것은 그것이 이 클라이언트의 구독 대상이라는 뜻이 아니다.
|
||||
|
||||
## 느슨한 검사와 조용한 실패의 조합
|
||||
|
||||
이 검사가 막으려는 실패는 자바독이 스스로 "조용하다" 고 적은 것이다. 수정은 파서를 들이지 않고도 가능하다 — `"channel_creds"` 를 포함하는 객체 범위 안에서 `"type"` 값을 찾는 정도의 구조 인식이면 두 번째 검사가 실제 조건에 가까워진다. 또는 파서를 테스트 범위에만 두고 이 가드는 형태를 좁힌 정규식으로 바꾼다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
실제 xDS 통제 평면을 세워 부트스트랩 대조를 재현하지 않았다. 세 검사의 문자열 포함 조건으로 판정했다.
|
||||
|
||||
<!-- body:end -->
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: grpc-client-f04
|
||||
title: 프로파일 검증기가 javadoc 이 든 두 실수 중 하나만 검사한다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:grpc-client-f04
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: grpc-client-f04
|
||||
file: ../../../final/evidence/rendered/grpc-client-f04.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/grpc-client-f04.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/grpc/grpc-client.md#L199 이다.
|
||||
module: grpc-client
|
||||
priority: P3
|
||||
---
|
||||
|
||||
# 프로파일 검증기가 javadoc 이 든 두 실수 중 하나만 검사한다
|
||||
|
||||
구현된 것은 첫째와 다른 것이다. 이름이 같은 프로파일이 두 번 선언된 경우를 잡는다.
|
||||
|
||||
## 문제
|
||||
|
||||
구현된 것은 첫째와 다른 것이다.
|
||||
|
||||
이름이 같은 프로파일이 두 번 선언된 경우를 잡는다.
|
||||
|
||||
## 결론
|
||||
|
||||
javadoc 이 든 둘째는 이름이 다르고 대상이 같은 경우인데, 그 검사가 없다.
|
||||
|
||||
지도는 이름을 키로 쓰므로 같은 대상을 가리키는 두 이름은 서로를 만나지 않는다.
|
||||
|
||||
그리고 둘째가 실제로 더 찾기 어려운 형태다 — 이름이 같으면 설정 결속이 먼저 실패하거나 나중 것이 이기지만, 이름이 다르면 조용히 두 채널이 생긴다.
|
||||
|
||||
수정은 대상과 설정을 키로 하는 두 번째 지도를 두고 역방향 중복을 보고하는 것이다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12 java -version 으로 확인
|
||||
Gradle : 9.0.0 src/gradle/wrapper/gradle-wrapper.properties 의 distributionUrl 로 확인
|
||||
확인 방식 : javadoc 이 든 두 실수와 검증기에 실제로 구현된 검사의 대조
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 analysis/grpc/grpc-client.md#L199 에 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
구현된 것은 javadoc 이 든 첫째와 **다른 것**이다 — 이름이 같은 프로파일이 두 번 선언된 경우를 잡는다.
|
||||
|
||||
## 검증기가 실제로 잡는 실수
|
||||
|
||||
:::evidence key="grpc-client-f04" alt="분석 문서 analysis/grpc/grpc-client.md 에서 이 기록의 근거 절을 그대로 잘라낸 15줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="analysis/grpc/grpc-client.md 발췌 — 15줄" zoom="true"
|
||||
:::
|
||||
|
||||
## 둘째에 해당하는 검사가 없다
|
||||
|
||||
javadoc 이 든 둘째는 **이름이 다르고 대상이 같은** 경우인데, 지도는 이름을 키로 쓰므로 같은 대상을 가리키는 두 이름은 서로를 만나지 않는다.
|
||||
|
||||
## 둘째가 더 찾기 어려운 형태다
|
||||
|
||||
이름이 같으면 설정 결속이 먼저 실패하거나 나중 것이 이기지만, 이름이 다르면 조용히 두 채널이 생긴다. 수정은 대상과 설정을 키로 하는 두 번째 지도를 두고 역방향 중복을 보고하는 것이다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
검증되지 않는 둘째 실수를 담은 프로파일로 시작을 시도하지 않았다. 검증기 본문의 검사 목록으로 판정했다.
|
||||
|
||||
<!-- body:end -->
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: grpc-discovery-f02
|
||||
title: 리졸버 검증기의 규칙이 하나뿐인데 javadoc 은 복수형으로 서술한다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:grpc-discovery-f02
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: grpc-discovery-f02
|
||||
file: ../../../final/evidence/rendered/grpc-discovery-f02.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/grpc-discovery-f02.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/grpc/grpc-discovery.md#L177 이다.
|
||||
module: grpc-discovery
|
||||
priority: P3
|
||||
---
|
||||
|
||||
# 리졸버 검증기의 규칙이 하나뿐인데 javadoc 은 복수형으로 서술한다
|
||||
|
||||
javadoc 은 "Checks a discovery configuration for the things that look right and are not" 라고 적는다. 실제로 담긴 규칙은 균형 정책의 무의미함 하나다.
|
||||
|
||||
## 문제
|
||||
|
||||
javadoc 은 "Checks a discovery configuration for the things that look right and are not" 라고 적는다.
|
||||
|
||||
실제로 담긴 규칙은 균형 정책의 무의미함 하나다.
|
||||
|
||||
## 결론
|
||||
|
||||
나머지 위험 조합은 GrpcResolverProfile 정규 생성자가 이미 거부하므로 결과적으로 빈틈은 아니다.
|
||||
|
||||
다만 목록으로 보고하는 API 형태와 규칙 하나라는 내용이 어긋나 있어, 다음 사람이 여기에 규칙을 더할 자리로 읽거나 이미 여러 규칙이 있다고 읽는다.
|
||||
|
||||
§17.1 이 실제로 그 자리다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12 java -version 으로 확인
|
||||
Gradle : 9.0.0 src/gradle/wrapper/gradle-wrapper.properties 의 distributionUrl 로 확인
|
||||
확인 방식 : GrpcResolverProfile 참조 16건 검색과 검증기에 실제로 담긴 규칙 수 확인
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 analysis/grpc/grpc-discovery.md#L177 에 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
javadoc 은 "Checks a discovery configuration for **the things** that look right and are not" 라고 적는다. 실제로 담긴 규칙은 균형 정책의 무의미함 하나다.
|
||||
|
||||
## GrpcResolverProfile 참조 위치
|
||||
|
||||
:::evidence key="grpc-discovery-f02" alt="코드베이스에서 GrpcResolverProfile 를 검색한 출력 16줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="GrpcResolverProfile 코드베이스 검색 — 16줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## 결과적으로 빈틈은 아니다
|
||||
|
||||
나머지 위험 조합은 `GrpcResolverProfile` 정규 생성자가 이미 거부한다.
|
||||
|
||||
## 어긋난 것은 형태와 내용이다
|
||||
|
||||
목록으로 보고하는 API 형태와 규칙 하나라는 내용이 어긋나 있어, 다음 사람이 여기에 규칙을 더할 자리로 읽거나 이미 여러 규칙이 있다고 읽는다. §17.1 이 실제로 그 자리다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
시작 검증기를 통한 스킴 거부를 실행으로 확인하지 않았다. 그 검증기가 돌지 않는다.
|
||||
|
||||
<!-- body:end -->
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: grpc-observability-f02
|
||||
title: deadlineRemaining 도 meter 가 없다. javadoc 은 그것이 기록된다고 말한다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:grpc-observability-f02
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: grpc-observability-f02
|
||||
file: ../../../final/evidence/rendered/grpc-observability-f02.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/grpc-observability-f02.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/grpc/grpc-observability.md#L213 이다.
|
||||
module: grpc-observability
|
||||
priority: P3
|
||||
---
|
||||
|
||||
# deadlineRemaining 도 meter 가 없다. javadoc 은 그것이 기록된다고 말한다
|
||||
|
||||
§17.1 과 같은 형태가 GrpcRpcObservation 에도 있고, 이쪽은 클래스 javadoc 이 명시적으로 어긋난다. 두 값을 함께 들면서 "기록된다"고 단언하는데, record(GrpcRpcObservation) 이 등록하는 meter 는 넷이다.
|
||||
|
||||
## 문제
|
||||
|
||||
§17.1 과 같은 형태가 GrpcRpcObservation 에도 있고, 이쪽은 클래스 javadoc 이 명시적으로 어긋난다.
|
||||
|
||||
두 값을 함께 들면서 "기록된다"고 단언하는데, record(GrpcRpcObservation) 이 등록하는 meter 는 넷이다.
|
||||
|
||||
## 결론
|
||||
|
||||
queueWaitTime 은 QUEUE_WAIT 타이머로 나간다.
|
||||
|
||||
deadlineRemaining 은 나가는 곳이 없다 — meter 이름 상수 일곱 개 중에도 마감 잔량에 해당하는 것이 없고, tags() 에도 들어가지 않는다(태그로 넣으면 카디널리티가 터지므로 그것이 옳다).
|
||||
|
||||
그래서 이 성분을 읽는 코드는 unusedDeadline() 하나이고, 그 메서드의 production 호출자는 0 이다(§12.1).
|
||||
|
||||
§4.6 은 이 성분의 검증 비대칭(음수 허용)이 "마감을 넘긴 호출을 표현하기 위한 것" 이라고 읽었다.
|
||||
|
||||
그 해석은 그대로 유효하다 — 다만 그 표현이 도달하는 곳이 아직 없다.
|
||||
|
||||
관측값으로서는 §17.1 의 queueHighWatermark 와 같은 처지다.
|
||||
|
||||
queueWaitTime 과 같은 형태로 타이머를 하나 더 둔다(마감을 넘긴 경우는 unusedDeadline() 이 이미 빈 값으로 구분해 주므로 기록 대상에서 빼면 된다).
|
||||
|
||||
아니면 javadoc 의 "recorded" 를 "carried" 로 낮춘다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12 java -version 으로 확인
|
||||
Gradle : 9.0.0 src/gradle/wrapper/gradle-wrapper.properties 의 distributionUrl 로 확인
|
||||
확인 방식 : GrpcRpcObservation 참조 5건 검색과 클래스 javadoc 의 기록 주장 대비 meter 넷 대조
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 analysis/grpc/grpc-observability.md#L213 에 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
§17.1 과 같은 형태가 `GrpcRpcObservation` 에도 있고, 이쪽은 클래스 javadoc 이 명시적으로 어긋난다.
|
||||
|
||||
> "{@code deadlineRemaining} and {@code queueWaitTime} are **recorded** because they are the two numbers that explain a latency change without being latency."
|
||||
|
||||
## GrpcRpcObservation 참조 위치
|
||||
|
||||
:::evidence key="grpc-observability-f02" alt="코드베이스에서 GrpcRpcObservation 를 검색한 출력 5줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="GrpcRpcObservation 코드베이스 검색 — 5줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## 둘 중 하나만 나간다
|
||||
|
||||
`record(GrpcRpcObservation)` 이 등록하는 meter 는 넷이고, `queueWaitTime` 은 `QUEUE_WAIT` 타이머로 나간다. `deadlineRemaining` 은 나가는 곳이 없다 — meter 이름 상수 일곱 개 중에도 마감 잔량에 해당하는 것이 없고, `tags()` 에도 들어가지 않는다(태그로 넣으면 카디널리티가 터지므로 그것이 옳다).
|
||||
|
||||
## 읽는 코드가 하나이고 그 호출자가 0이다
|
||||
|
||||
`unusedDeadline()` 하나이고 production 호출자는 0 이다(§12.1). §4.6 은 이 성분의 검증 비대칭(음수 허용)이 "마감을 넘긴 호출을 표현하기 위한 것" 이라고 읽었다 — 그 해석은 그대로 유효하고, 다만 그 표현이 도달하는 곳이 아직 없다.
|
||||
|
||||
## 수정
|
||||
|
||||
`queueWaitTime` 과 같은 형태로 타이머를 하나 더 둔다(마감을 넘긴 경우는 `unusedDeadline()` 이 이미 빈 값으로 구분해 준다). 아니면 javadoc 의 "recorded" 를 "carried" 로 낮춘다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
meter 이름 상수 일곱 개와 두 record 오버로드 본문으로 판정했다. 다른 이름의 상수가 그 역할을 겸하는지는 이름만 보고 배제했다.
|
||||
|
||||
<!-- body:end -->
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: grpc-policy-f06
|
||||
title: 오류 노출 거부 목록의 "호스트와 포트" 규칙이 IPv4 점표기만 본다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:grpc-policy-f06
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: grpc-policy-f06
|
||||
file: ../../../final/evidence/rendered/grpc-policy-f06.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/grpc-policy-f06.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/grpc/grpc-policy.md#L305 이다.
|
||||
module: grpc-policy
|
||||
priority: P3
|
||||
---
|
||||
|
||||
# 오류 노출 거부 목록의 "호스트와 포트" 규칙이 IPv4 점표기만 본다
|
||||
|
||||
아홉 패턴을 전부 읽으면 주소 형태를 보는 것은 이 하나다. 클래스 javadoc 은 거부 대상을 "a stack frame, a SQL fragment, a JDBC URL, a bearer token, a host and port, a file path" 로 서술하는데, 실제로 걸리는 host 는 IPv4 점표기뿐이다.
|
||||
|
||||
## 문제
|
||||
|
||||
아홉 패턴을 전부 읽으면 주소 형태를 보는 것은 이 하나다.
|
||||
|
||||
클래스 javadoc 은 거부 대상을 "a stack frame, a SQL fragment, a JDBC URL, a bearer token, a host and port, a file path" 로 서술하는데, 실제로 걸리는 host 는 IPv4 점표기뿐이다.
|
||||
|
||||
## 결론
|
||||
|
||||
IPv6 리터럴 — fe80::1, [2001:db8::1]:5432 DNS 이름과 포트 — documents-db.internal:5432, kafka-0.kafka-headless:9092 jdbc:postgresql://db/app 이 막히는 것은 host 규칙이 아니라 jdbc: 규칙 때문이다.
|
||||
|
||||
즉 이 구멍은 테스트에도 없다 — exposurePolicyRefusesLeakyStrings 의 아홉 사례 중 주소는 upstream 10.0.3.14:5432 refused 하나이고 IPv4 다.
|
||||
|
||||
닿는 경로는 mapUnknown 이다.
|
||||
|
||||
인식되지 않은 예외의 메시지를 safeToExpose 가 통과시키면 그대로 클라이언트로 간다.
|
||||
|
||||
IPv6 클러스터나 쿠버네티스 서비스 이름을 쓰는 배포에서 상류 좌표가 밖으로 나간다.
|
||||
|
||||
등급이 P3 인 이유는 두 가지다.
|
||||
|
||||
이 리프가 build-only 라 오늘 닿지 않고, 노출되는 것이 자격증명이 아니라 내부 좌표다.
|
||||
|
||||
다만 이 정책이 존재하는 이유 자체가 "부분 마스킹이 아니라 통째 교체" 이므로, 목록에 빠진 형태는 통째로 통과한다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12 java -version 으로 확인
|
||||
Gradle : 9.0.0 src/gradle/wrapper/gradle-wrapper.properties 의 distributionUrl 로 확인
|
||||
확인 방식 : 거부 목록 아홉 패턴 전수 확인과 클래스 javadoc 의 거부 대상 서술 대조
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 analysis/grpc/grpc-policy.md#L305 에 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
아홉 패턴을 전부 읽으면 주소 형태를 보는 것은 하나이고 그것이 IPv4 점표기만 본다. 클래스 javadoc 은 거부 대상을 "a stack frame, a SQL fragment, a JDBC URL, a bearer token, **a host and port**, a file path" 로 서술한다.
|
||||
|
||||
## 아홉 패턴 중 주소를 보는 하나
|
||||
|
||||
:::evidence key="grpc-policy-f06" alt="분석 문서 analysis/grpc/grpc-policy.md 에서 이 기록의 근거 절을 그대로 잘라낸 15줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="analysis/grpc/grpc-policy.md 발췌 — 15줄" zoom="true"
|
||||
:::
|
||||
|
||||
## 걸리지 않는 형태들
|
||||
|
||||
IPv6 리터럴(`fe80::1`, `[2001:db8::1]:5432`)과 DNS 이름과 포트(`documents-db.internal:5432`, `kafka-0.kafka-headless:9092`)다. `jdbc:postgresql://db/app` 이 막히는 것은 host 규칙이 아니라 `jdbc:` 규칙 때문이다.
|
||||
|
||||
## 테스트에도 없다
|
||||
|
||||
`exposurePolicyRefusesLeakyStrings` 의 아홉 사례 중 주소는 `upstream 10.0.3.14:5432 refused` 하나이고 IPv4 다.
|
||||
|
||||
## 닿는 경로
|
||||
|
||||
`mapUnknown` 이다. 인식되지 않은 예외의 메시지를 `safeToExpose` 가 통과시키면 그대로 클라이언트로 간다. IPv6 클러스터나 쿠버네티스 서비스 이름을 쓰는 배포에서 상류 좌표가 밖으로 나간다.
|
||||
|
||||
## 등급이 P3 인 이유 둘
|
||||
|
||||
이 리프가 build-only 라 오늘 닿지 않고, 노출되는 것이 자격증명이 아니라 내부 좌표다. 다만 이 정책이 존재하는 이유 자체가 "부분 마스킹이 아니라 통째 교체" 이므로, 목록에 빠진 형태는 통째로 통과한다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
IPv6 누출을 실제 예외 메시지로 재현하지 않았다. 아홉 패턴 중 IPv4 점표기 외에 주소 형태를 보는 것이 없음을 확인해 판정했다.
|
||||
|
||||
<!-- body:end -->
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: grpc-proto-contract-f02
|
||||
title: 반환 목록이 자바독이 약속한 source order 가 아니다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:grpc-proto-contract-f02
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: grpc-proto-contract-f02
|
||||
file: ../../../final/evidence/rendered/grpc-proto-contract-f02.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/grpc-proto-contract-f02.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/grpc/grpc-proto-contract.md#L191 이다.
|
||||
module: grpc-proto-contract
|
||||
priority: P3
|
||||
---
|
||||
|
||||
# 반환 목록이 자바독이 약속한 source order 가 아니다
|
||||
|
||||
validate 의 javadoc 은 "@return every violation found, in source order" 라고 적는다. 실제로는 파일 앞머리의 syntax·package 위반이 40번째 줄의 map 위반보다 뒤에 온다.
|
||||
|
||||
## 문제
|
||||
|
||||
validate 의 javadoc 은 "@return every violation found, in source order" 라고 적는다.
|
||||
|
||||
실제로는 파일 앞머리의 syntax·package 위반이 40번째 줄의 map 위반보다 뒤에 온다.
|
||||
|
||||
## 결론
|
||||
|
||||
describe() 가 file:line rule — detail 형태를 만들고 그 형태의 목적이 빌드 로그를 읽는 것이므로, 정렬이 어긋나면 리뷰 목록으로서의 값이 줄어든다.
|
||||
|
||||
수정은 반환 직전에 line 으로 안정 정렬하는 것이다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12 java -version 으로 확인
|
||||
Gradle : 9.0.0 src/gradle/wrapper/gradle-wrapper.properties 의 distributionUrl 로 확인
|
||||
확인 방식 : validate 의 javadoc 이 약속한 정렬과 실제 위반 수집 순서의 대조
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 analysis/grpc/grpc-proto-contract.md#L191 에 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
`validate` 의 javadoc 은 "@return every violation found, **in source order**" 라고 적는다. 실제로는 파일 앞머리의 `syntax`·`package` 위반이 40번째 줄의 `map` 위반보다 뒤에 온다.
|
||||
|
||||
## javadoc 이 약속한 정렬
|
||||
|
||||
:::evidence key="grpc-proto-contract-f02" alt="분석 문서 analysis/grpc/grpc-proto-contract.md 에서 이 기록의 근거 절을 그대로 잘라낸 15줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="analysis/grpc/grpc-proto-contract.md 발췌 — 15줄" zoom="true"
|
||||
:::
|
||||
|
||||
## 그 형태의 목적이 빌드 로그를 읽는 것이다
|
||||
|
||||
`describe()` 가 `file:line rule — detail` 형태를 만들므로, 정렬이 어긋나면 리뷰 목록으로서의 값이 줄어든다. 수정은 반환 직전에 `line` 으로 안정 정렬하는 것이다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
실제 빌드 로그에서 순서 뒤바뀜을 관측하지 않았다. 수집 순서 코드로 판정했다.
|
||||
|
||||
<!-- body:end -->
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: grpc-server-f02
|
||||
title: 원시 API 규칙이 import 문만 보므로 완전 수식 사용과 와일드카드를 놓친다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:grpc-server-f02
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: grpc-server-f02
|
||||
file: ../../../final/evidence/rendered/grpc-server-f02.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/grpc-server-f02.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/grpc/grpc-server.md#L167 이다.
|
||||
module: grpc-server
|
||||
priority: P3
|
||||
---
|
||||
|
||||
# 원시 API 규칙이 import 문만 보므로 완전 수식 사용과 와일드카드를 놓친다
|
||||
|
||||
이것이 가정에 그치지 않는 이유는 이 저장소 자신의 문체다. 같은 가족의 여러 파일이 완전 수식 참조를 본문에 그대로 쓴다.
|
||||
|
||||
## 문제
|
||||
|
||||
이것이 가정에 그치지 않는 이유는 이 저장소 자신의 문체다.
|
||||
|
||||
같은 가족의 여러 파일이 완전 수식 참조를 본문에 그대로 쓴다.
|
||||
|
||||
## 결론
|
||||
|
||||
즉 이 코드베이스에서 완전 수식 사용은 예외가 아니라 흔한 형태다.
|
||||
|
||||
규칙 클래스의 자바독은 "there is nothing to reach for" 를 목표로 든다.
|
||||
|
||||
지금 형태는 손이 닿는 경로 하나만 본다.
|
||||
|
||||
수정은 정규식을 타입 이름의 등장 자체로 넓히거나(오탐이 생기므로 주석·문자열 제거가 필요), 바이트코드 기반 검사로 옮기는 것이다.
|
||||
|
||||
후자가 이 저장소의 다른 아키텍처 게이트와 형태가 같다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12 java -version 으로 확인
|
||||
Gradle : 9.0.0 src/gradle/wrapper/gradle-wrapper.properties 의 distributionUrl 로 확인
|
||||
확인 방식 : 규칙이 스캔하는 구문 범위와 같은 가족 파일들의 완전 수식 참조 사용 대조
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 analysis/grpc/grpc-server.md#L167 에 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
원시 API 규칙이 import 문만 보므로 완전 수식 사용과 와일드카드를 놓친다.
|
||||
|
||||
## 규칙이 보는 범위
|
||||
|
||||
:::evidence key="grpc-server-f02" alt="분석 문서 analysis/grpc/grpc-server.md 에서 이 기록의 근거 절을 그대로 잘라낸 15줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="analysis/grpc/grpc-server.md 발췌 — 15줄" zoom="true"
|
||||
:::
|
||||
|
||||
## 가정에 그치지 않는 이유는 이 저장소 자신의 문체다
|
||||
|
||||
같은 가족의 여러 파일이 완전 수식 참조를 본문에 그대로 쓴다. 즉 이 코드베이스에서 완전 수식 사용은 예외가 아니라 흔한 형태다.
|
||||
|
||||
## 규칙의 목표와 어긋난다
|
||||
|
||||
규칙 클래스의 자바독은 "there is nothing to reach for" 를 목표로 든다. 지금 형태는 손이 닿는 경로 하나만 본다. 수정은 정규식을 타입 이름의 등장 자체로 넓히거나(오탐이 생기므로 주석·문자열 제거가 필요), 바이트코드 기반 검사로 옮기는 것이다 — 후자가 이 저장소의 다른 아키텍처 게이트와 형태가 같다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
완전 수식 사용을 담은 파일로 규칙을 실행해 통과를 관측하지 않았다. 규칙이 import 문만 본다는 코드 형태로 판정했다.
|
||||
|
||||
<!-- body:end -->
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: messaging-kafka-f01
|
||||
title: deduplicatedPublish 를 문서는 지원으로 적고 코드는 거짓으로 둔다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:messaging-kafka-f01
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: messaging-kafka-f01
|
||||
file: ../../../final/evidence/rendered/messaging-kafka-f01.svg
|
||||
- key: messaging-kafka-f01-diagram
|
||||
file: ../../../final/assets/diagrams/messaging-kafka-f01.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/messaging-kafka-f01.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/messaging/messaging-kafka.md#L208 이다.
|
||||
module: messaging-kafka
|
||||
priority: P1
|
||||
---
|
||||
|
||||
# deduplicatedPublish 를 문서는 지원으로 적고 코드는 거짓으로 둔다
|
||||
|
||||
코드의 판정이 옳고 그 근거가 javadoc 에 있다. docs/messaging/support-matrix.md:55 의 능력 표는 이 칸을 O 로 적는다.
|
||||
|
||||
## 문제
|
||||
|
||||
코드의 판정이 옳고 그 근거가 javadoc 에 있다.
|
||||
|
||||
docs/messaging/support-matrix.md:55 의 능력 표는 이 칸을 O 로 적는다.
|
||||
|
||||
## 결론
|
||||
|
||||
그 차이가 무거운 이유는 이 플랫폼에서 이 플래그가 특별하기 때문이다.
|
||||
|
||||
능력 열둘 중 부재가 예외를 만드는 유일한 플래그다.
|
||||
|
||||
그래서 표를 읽고 중복 제거를 전제한 목적지를 설계한 팀은 실행 시점에 능력 예외를 만난다.
|
||||
|
||||
반대로 표를 읽고 "중복 제거가 있으니 모호를 그냥 재시도해도 된다" 고 결론지으면, 실제로는 중복이 저장된다.
|
||||
|
||||
MessagingCapabilities 의 클래스 javadoc 이 그 피해를 미리 적는다 — "a silently weakened guarantee is indistinguishable from a working one until the incident." 수정은 문서 쪽이다.
|
||||
|
||||
코드가 이미 옳다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12 java -version 으로 확인
|
||||
Gradle : 9.0.0 src/gradle/wrapper/gradle-wrapper.properties 의 distributionUrl 로 확인
|
||||
확인 방식 : MessagingCapabilities 참조 39건 검색과 지원 문서 능력 표의 해당 칸 대조
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 analysis/messaging/messaging-kafka.md#L208 에 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
코드의 판정이 옳고 그 근거가 javadoc 에 있다. `docs/messaging/support-matrix.md:55` 의 능력 표는 이 칸을 `O` 로 적는다.
|
||||
|
||||
## 문서와 상수가 갈리는 칸
|
||||
|
||||
:::evidence key="messaging-kafka-f01-diagram" alt="지원 문서 쪽에 deduplicatedPublish 지원과 표를 읽은 설계가 놓이고 코드 상수 쪽에 거짓과 실행 시점 능력 예외가 빗금으로 놓인다" caption="문서와 상수가 갈리는 칸" zoom="false"
|
||||
:::
|
||||
|
||||
## MessagingCapabilities 참조 위치
|
||||
|
||||
:::evidence key="messaging-kafka-f01" alt="코드베이스에서 MessagingCapabilities 를 검색한 출력 39줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="MessagingCapabilities 코드베이스 검색 — 39줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## 이 플래그가 특별해서 차이가 무겁다
|
||||
|
||||
능력 열둘 중 **부재가 예외를 만드는 유일한 플래그**다. 그래서 표를 읽고 중복 제거를 전제한 목적지를 설계한 팀은 실행 시점에 능력 예외를 만난다. 반대로 표를 읽고 "중복 제거가 있으니 모호를 그냥 재시도해도 된다" 고 결론지으면, 실제로는 중복이 저장된다.
|
||||
|
||||
## 수정은 문서 쪽이다
|
||||
|
||||
`MessagingCapabilities` 의 클래스 javadoc 이 그 피해를 미리 적는다 — "a silently weakened guarantee is indistinguishable from a working one until the incident." 코드가 이미 옳다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
실제 브로커로 이 능력을 켠 소비자를 만들어 중복 도착을 관측하지 않았다. 문서와 코드 상수의 대조로 판정했다.
|
||||
|
||||
<!-- body:end -->
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: messaging-observability-f01
|
||||
title: 태그 어휘가 존재하고 유일한 호출부가 우회해, 실패 분류가 기록되지 않는다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:messaging-observability-f01
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: messaging-observability-f01
|
||||
file: ../../../final/evidence/rendered/messaging-observability-f01.svg
|
||||
- key: messaging-observability-f01-diagram
|
||||
file: ../../../final/assets/diagrams/messaging-observability-f01.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/messaging-observability-f01.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/messaging/messaging-observability.md#L673 이다.
|
||||
module: messaging-observability
|
||||
priority: P2
|
||||
---
|
||||
|
||||
# 태그 어휘가 존재하고 유일한 호출부가 우회해, 실패 분류가 기록되지 않는다
|
||||
|
||||
DefaultMessagingObservationConvention은 소비자가 0이다. 유일한 production 호출부(DefaultMessagePublisher.observe:260-269)가 "publish" 리터럴과 4인자 MessagingTags.of(...)를 쓴다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **타입이 문서화한 불변식은 타입이 강제한다**
|
||||
같은 분석 리프에서 끌어낸 규칙이다.
|
||||
|
||||
## 문제
|
||||
|
||||
DefaultMessagingObservationConvention은 소비자가 0이다.
|
||||
|
||||
유일한 production 호출부(DefaultMessagePublisher.observe:260-269)가 "publish" 리터럴과 4인자 MessagingTags.of(...)를 쓴다.
|
||||
|
||||
## 결론
|
||||
|
||||
그 factory는 failureCategory와 retryStage를 NONE으로 고정한다.
|
||||
|
||||
convention의 publish(broker, dest, completion, Optional<FailureCategory>)는 정확히 failureCategory를 채우려고 존재한다.
|
||||
|
||||
convention javadoc이 "an adapter inventing its own spelling of 'rejected' silently breaks every alert that was watching for it"를 이유로 중앙화를 선언했고, 첫 호출부가 그것을 지나쳤다.
|
||||
|
||||
그리고 결과가 철자 문제에 그치지 않는다 — MessagingTags가 선언한 6차원 중 4개만 채워진다.
|
||||
|
||||
메트릭이 배선되더라도(§다음 항목) 실패한 발행이 failureCategory=none으로 기록되어, "왜 실패했는가"를 메트릭에서 나눌 수 없다.
|
||||
|
||||
PublishResult.failure()에 FailureDescriptor가 이미 있으므로 값은 손에 있다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12 java -version 으로 확인
|
||||
Gradle : 9.0.0 src/gradle/wrapper/gradle-wrapper.properties 의 distributionUrl 로 확인
|
||||
확인 방식 : DefaultMessagingObservationConvention 참조 1건 검색과 유일한 production 호출부가 넘기는 인자 확인
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 analysis/messaging/messaging-observability.md#L673 에 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
`DefaultMessagingObservationConvention`은 소비자가 0이다. 유일한 production 호출부(`DefaultMessagePublisher.observe:260-269`)가 `"publish"` 리터럴과 **4인자** `MessagingTags.of(...)`를 쓴다.
|
||||
|
||||
## 기록이 끊기는 자리
|
||||
|
||||
:::evidence key="messaging-observability-f01-diagram" alt="convention 이 채우는 것 쪽에 여섯 차원과 failureCategory 가 놓이고 호출부가 채우는 것 쪽에 네 차원과 NONE 고정이 빗금으로 놓인다" caption="기록이 끊기는 자리" zoom="false"
|
||||
:::
|
||||
|
||||
그 factory는 `failureCategory`와 `retryStage`를 `NONE`으로 고정한다.
|
||||
|
||||
## DefaultMessagingObservationConvention 참조 위치
|
||||
|
||||
:::evidence key="messaging-observability-f01" alt="코드베이스에서 DefaultMessagingObservationConvention 를 검색한 출력 1줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="DefaultMessagingObservationConvention 코드베이스 검색 — 1줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## convention 이 존재하는 이유가 그 필드다
|
||||
|
||||
`publish(broker, dest, completion, Optional<FailureCategory>)`는 정확히 `failureCategory`를 채우려고 존재한다. convention javadoc이 "an adapter inventing its own spelling of 'rejected' silently breaks every alert that was watching for it"를 이유로 중앙화를 선언했고, 첫 호출부가 그것을 지나쳤다.
|
||||
|
||||
## 철자 문제에 그치지 않는다
|
||||
|
||||
`MessagingTags`가 선언한 6차원 중 4개만 채워진다. 메트릭이 배선되더라도 실패한 발행이 `failureCategory=none`으로 기록되어, "왜 실패했는가"를 메트릭에서 나눌 수 없다. `PublishResult.failure()`에 `FailureDescriptor`가 이미 있으므로 값은 손에 있다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
실제 MeterRegistry 에 붙여 태그가 어떻게 기록되는지 관측하지 않았다. 호출부의 인자 수와 리터럴로 판정했다.
|
||||
|
||||
<!-- body:end -->
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: messaging-outbox-jdbc-postgresql-f06
|
||||
title: 백오프 지터가 인스턴스를 분산시키지 못한다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:messaging-outbox-jdbc-postgresql-f06
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: messaging-outbox-jdbc-postgresql-f06
|
||||
file: ../../../final/evidence/rendered/messaging-outbox-jdbc-postgresql-f06.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/messaging-outbox-jdbc-postgresql-f06.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/messaging/messaging-outbox-jdbc-postgresql.md#L923 이다.
|
||||
module: messaging-outbox-jdbc-postgresql
|
||||
priority: P3
|
||||
---
|
||||
|
||||
# 백오프 지터가 인스턴스를 분산시키지 못한다
|
||||
|
||||
jittered = capped - (capped/8) * (exponent % 3) 는 exponent 만의 함수다. 같은 상태의 복제본들은 같은 값을 계산한다.
|
||||
|
||||
## 문제
|
||||
|
||||
jittered = capped - (capped/8) * (exponent % 3) 는 exponent 만의 함수다.
|
||||
|
||||
같은 상태의 복제본들은 같은 값을 계산한다.
|
||||
|
||||
## 결론
|
||||
|
||||
javadoc 이 약속하는 "thundering herd 방지" 가 성립하지 않는다.
|
||||
|
||||
OutboxRelay 가 이미 defaultOwner() 로 프로세스별 안정 식별자를 만든다(pid@uuid8).
|
||||
|
||||
그것의 해시를 지터에 섞으면 결정성(같은 프로세스에서 재현 가능)을 유지하면서 인스턴스 간 위상차가 생긴다.
|
||||
|
||||
javadoc 이 난수를 거부한 이유("a random source would make the schedule impossible to test")도 그대로 지켜진다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12 java -version 으로 확인
|
||||
Gradle : 9.0.0 src/gradle/wrapper/gradle-wrapper.properties 의 distributionUrl 로 확인
|
||||
확인 방식 : 지터 계산식의 입력 변수 확인 — exponent 만의 함수임을 코드로 대조
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 analysis/messaging/messaging-outbox-jdbc-postgresql.md#L923 에 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
`jittered = capped - (capped/8) * (exponent % 3)` 는 `exponent` 만의 함수다. 같은 상태의 복제본들은 같은 값을 계산한다.
|
||||
|
||||
## OutboxRelay 참조 위치
|
||||
|
||||
:::evidence key="messaging-outbox-jdbc-postgresql-f06" alt="코드베이스에서 OutboxRelay 를 검색한 출력 27줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="OutboxRelay 코드베이스 검색 — 27줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## javadoc 이 약속한 성질이 성립하지 않는다
|
||||
|
||||
"thundering herd 방지" 다.
|
||||
|
||||
## 재료가 이미 있다
|
||||
|
||||
`OutboxRelay` 가 `defaultOwner()` 로 프로세스별 안정 식별자를 만든다(`pid@uuid8`). 그것의 해시를 지터에 섞으면 결정성(같은 프로세스에서 재현 가능)을 유지하면서 인스턴스 간 위상차가 생긴다. javadoc 이 난수를 거부한 이유("a random source would make the schedule impossible to test")도 그대로 지켜진다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
지터 동기화를 다중 인스턴스로 재현하지 않았다. 함수가 exponent 만의 함수라는 것은 코드로 확인했다.
|
||||
|
||||
<!-- body:end -->
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: messaging-outbox-jdbc-postgresql-f07
|
||||
title: 커넥션 획득 방식이 리프 안에서 갈린다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:messaging-outbox-jdbc-postgresql-f07
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: messaging-outbox-jdbc-postgresql-f07
|
||||
file: ../../../final/evidence/rendered/messaging-outbox-jdbc-postgresql-f07.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/messaging-outbox-jdbc-postgresql-f07.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/messaging/messaging-outbox-jdbc-postgresql.md#L929 이다.
|
||||
module: messaging-outbox-jdbc-postgresql
|
||||
priority: P3
|
||||
---
|
||||
|
||||
# 커넥션 획득 방식이 리프 안에서 갈린다
|
||||
|
||||
JdbcOutboxRepository.append 는 DataSourceUtils, 나머지는 raw dataSource.getConnection(), JdbcAdminOperationJournal 은 전부 DataSourceUtils. 릴레이 연산이 비즈니스 트랜잭션에 합류하면 안 된다는 판단은 타당하지만 어디에도 적혀 있지 않고, 같은 리프의 저널이 반대로 한다.
|
||||
|
||||
## 문제
|
||||
|
||||
JdbcOutboxRepository.append 는 DataSourceUtils, 나머지는 raw dataSource.getConnection(), JdbcAdminOperationJournal 은 전부 DataSourceUtils.
|
||||
|
||||
릴레이 연산이 비즈니스 트랜잭션에 합류하면 안 된다는 판단은 타당하지만 어디에도 적혀 있지 않고, 같은 리프의 저널이 반대로 한다.
|
||||
|
||||
## 결론
|
||||
|
||||
withConnection 에 한 문장 — "릴레이 연산은 호출자 트랜잭션에 합류하지 않는다" — 을 붙이면 append 의 상세한 주석과 짝이 맞는다.
|
||||
|
||||
저널이 DataSourceUtils 를 쓰는 것이 의도인지도 확인이 필요하다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12 java -version 으로 확인
|
||||
Gradle : 9.0.0 src/gradle/wrapper/gradle-wrapper.properties 의 distributionUrl 로 확인
|
||||
확인 방식 : JdbcOutboxRepository 참조 5건 검색과 리프 안 세 지점의 커넥션 획득 방식 대조
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 analysis/messaging/messaging-outbox-jdbc-postgresql.md#L929 에 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
`JdbcOutboxRepository.append` 는 `DataSourceUtils`, 나머지는 raw `dataSource.getConnection()`, `JdbcAdminOperationJournal` 은 전부 `DataSourceUtils` 를 쓴다.
|
||||
|
||||
## JdbcOutboxRepository 참조 위치
|
||||
|
||||
:::evidence key="messaging-outbox-jdbc-postgresql-f07" alt="코드베이스에서 JdbcOutboxRepository 를 검색한 출력 5줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="JdbcOutboxRepository 코드베이스 검색 — 5줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## 판단은 타당하고 어디에도 적혀 있지 않다
|
||||
|
||||
릴레이 연산이 비즈니스 트랜잭션에 합류하면 안 된다는 판단이다. 그리고 같은 리프의 저널이 반대로 한다.
|
||||
|
||||
## 수정
|
||||
|
||||
`withConnection` 에 한 문장 — "릴레이 연산은 호출자 트랜잭션에 합류하지 않는다" — 을 붙이면 `append` 의 상세한 주석과 짝이 맞는다. 저널이 `DataSourceUtils` 를 쓰는 것이 의도인지도 확인이 필요하다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
비즈니스 트랜잭션 합류 여부가 실제 동작에서 갈리는 것을 관측하지 않았다. 획득 방식의 코드 대조로 판정했다.
|
||||
|
||||
<!-- body:end -->
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: messaging-schema-avro-f01
|
||||
title: CI에서 돈다고 선언한 게이트를 부르는 CI가 없다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:messaging-schema-avro-f01
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: messaging-schema-avro-f01
|
||||
file: ../../../final/evidence/rendered/messaging-schema-avro-f01.svg
|
||||
- key: messaging-schema-avro-f01-diagram
|
||||
file: ../../../final/assets/diagrams/messaging-schema-avro-f01.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/messaging-schema-avro-f01.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/messaging/messaging-schema-avro.md#L524 이다.
|
||||
module: messaging-schema-avro
|
||||
priority: P2
|
||||
---
|
||||
|
||||
# CI에서 돈다고 선언한 게이트를 부르는 CI가 없다
|
||||
|
||||
AvroCompatibilityGate javadoc이 "Run in CI rather than at runtime"이라고 선언한다. 저장소 전체에서 이 클래스 참조는 자기 선언과 자기 테스트뿐이고, src/build.gradle의 9개 verifyMessaging* task 중 스키마 진화를 검사하는 것이 없다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **모드 enum을 분기 조건으로 쓰면 각 분기에 테스트를 둔다**
|
||||
같은 분석 리프에서 끌어낸 규칙이다.
|
||||
- **컬렉션 순서가 계약이면 양쪽에서 같은 방향으로 적는다**
|
||||
같은 분석 리프에서 끌어낸 규칙이다.
|
||||
- **안정 코드는 판단 단위로 정하고 구현 단위로 정하지 않는다**
|
||||
같은 분석 리프에서 끌어낸 규칙이다.
|
||||
|
||||
## 문제
|
||||
|
||||
AvroCompatibilityGate javadoc이 "Run in CI rather than at runtime"이라고 선언한다.
|
||||
|
||||
저장소 전체에서 이 클래스 참조는 자기 선언과 자기 테스트뿐이고, src/build.gradle의 9개 verifyMessaging* task 중 스키마 진화를 검사하는 것이 없다.
|
||||
|
||||
## 결론
|
||||
|
||||
게이트의 존재 이유가 "한 번 발행되면 보존 로그에 영구히 남는다"인데, 그 보호가 어느 파이프라인에도 붙어 있지 않다.
|
||||
|
||||
AvroMessageCodec의 미사용과 달리 이것은 membership으로 설명되지 않는다 — 런타임 편입 여부와 무관하게 CI 게이트는 붙었어야 한다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12 java -version 으로 확인
|
||||
Gradle : 9.0.0 src/gradle/wrapper/gradle-wrapper.properties 의 distributionUrl 로 확인
|
||||
확인 방식 : AvroCompatibilityGate 참조 3건 검색과 이 게이트를 부르는 Gradle 태스크·CI 워크플로 검색
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 analysis/messaging/messaging-schema-avro.md#L524 에 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
`AvroCompatibilityGate` javadoc이 "Run in CI rather than at runtime"이라고 선언한다.
|
||||
|
||||
## 선언과 호출의 거리
|
||||
|
||||
:::evidence key="messaging-schema-avro-f01-diagram" alt="자기 테스트만 게이트를 부르는 것 안에 놓이고 verifyMessaging 태스크와 CI 워크플로가 바깥에 빗금으로 놓인다" caption="선언과 호출의 거리" zoom="false"
|
||||
:::
|
||||
|
||||
저장소 전체에서 이 클래스 참조는 자기 선언과 자기 테스트뿐이고, `src/build.gradle`의 9개 `verifyMessaging*` task 중 스키마 진화를 검사하는 것이 없다.
|
||||
|
||||
## AvroCompatibilityGate 참조 위치
|
||||
|
||||
:::evidence key="messaging-schema-avro-f01" alt="코드베이스에서 AvroCompatibilityGate 를 검색한 출력 3줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="AvroCompatibilityGate 코드베이스 검색 — 3줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## membership 으로 설명되지 않는다
|
||||
|
||||
게이트의 존재 이유가 "한 번 발행되면 보존 로그에 영구히 남는다"인데, 그 보호가 어느 파이프라인에도 붙어 있지 않다. `AvroMessageCodec`의 미사용과 달리 런타임 편입 여부와 무관하게 CI 게이트는 붙었어야 한다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
파생 프로젝트가 이 게이트를 자기 CI 에서 부르는지 확인할 수 없었다. 저장소 안에 확인 수단이 없다.
|
||||
|
||||
<!-- body:end -->
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: messaging-spring-boot-starter-f02
|
||||
title: 출고되는 신뢰성 체인 전체가 아무도 공급하지 않는 빈 뒤에 있고, 그 사슬이 자기 클래스 안을 가리킨다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:messaging-spring-boot-starter-f02
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: messaging-spring-boot-starter-f02
|
||||
file: ../../../final/evidence/rendered/messaging-spring-boot-starter-f02.svg
|
||||
- key: messaging-spring-boot-starter-f02-diagram
|
||||
file: ../../../final/assets/diagrams/messaging-spring-boot-starter-f02.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/messaging-spring-boot-starter-f02.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/messaging/messaging-spring-boot-starter.md#L300 이다.
|
||||
module: messaging-spring-boot-starter
|
||||
priority: P2
|
||||
---
|
||||
|
||||
# 출고되는 신뢰성 체인 전체가 아무도 공급하지 않는 빈 뒤에 있고, 그 사슬이 자기 클래스 안을 가리킨다
|
||||
|
||||
여섯 빈 전부가 애플리케이션이 공급해야 하는 타입에 걸려 있다. 조건 자체는 옳고 근거도 정확하다 — 플랫폼이 기본 구현을 주면 조용히 엉뚱한 곳에, 또는 아무 데도 쓰지 않게 된다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **검증기는 발행이 아니라 주입이 강제다**
|
||||
같은 분석 리프에서 끌어낸 규칙이다.
|
||||
|
||||
## 문제
|
||||
|
||||
여섯 빈 전부가 애플리케이션이 공급해야 하는 타입에 걸려 있다.
|
||||
|
||||
조건 자체는 옳고 근거도 정확하다 — 플랫폼이 기본 구현을 주면 조용히 엉뚱한 곳에, 또는 아무 데도 쓰지 않게 된다.
|
||||
|
||||
## 결론
|
||||
|
||||
문제는 저장소 안에 그 타입을 공급하는 코드가 없다는 것이다.
|
||||
|
||||
messaging-outbox-jdbc-postgresql 의 JdbcOutboxRepository 는 스프링 스테레오타입도 @Bean 선언도 없고, new JdbcOutboxRepository 가 main 에 0 건이다.
|
||||
|
||||
그래서 이 스타터를 켠 배포는 발행 경로는 얻고 발신함 경로는 얻지 못하며, 그 사실이 시작 시점에 어떤 신호도 내지 않는다.
|
||||
|
||||
둘째와 셋째가 같은 설정 클래스 안에서 방금 선언된 빈의 존재를 조건으로 삼는다.
|
||||
|
||||
스프링은 @ConditionalOnBean 을 자동 설정 클래스에서만, 그리고 등록 순서에 의존하는 방식으로만 신뢰할 수 있다고 문서화한다.
|
||||
|
||||
지금은 첫 조건이 이미 거짓이라 결과가 드러나지 않는다.
|
||||
|
||||
발신함을 배선하는 순간 이 사슬이 실제로 평가된다.
|
||||
|
||||
같은 가족의 다른 결정과 대비된다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12 java -version 으로 확인
|
||||
Gradle : 9.0.0 src/gradle/wrapper/gradle-wrapper.properties 의 distributionUrl 로 확인
|
||||
확인 방식 : JdbcOutboxRepository 참조 5건 검색과 여섯 빈의 @ConditionalOnBean 대상 타입 확인
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 analysis/messaging/messaging-spring-boot-starter.md#L300 에 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
여섯 빈 전부가 애플리케이션이 공급해야 하는 타입에 걸려 있다. 조건 자체는 옳고 근거도 정확하다 — 플랫폼이 기본 구현을 주면 조용히 엉뚱한 곳에, 또는 아무 데도 쓰지 않게 된다.
|
||||
|
||||
## 조건이 가리키는 대상
|
||||
|
||||
:::evidence key="messaging-spring-boot-starter-f02-diagram" alt="애플리케이션 공급 타입만 조건이 보는 것 안에 놓이고 같은 클래스에서 방금 선언된 빈이 바깥에 빗금으로 놓인다" caption="조건이 가리키는 대상" zoom="false"
|
||||
:::
|
||||
|
||||
문제는 저장소 안에 그 타입을 공급하는 코드가 없다는 것이다. `messaging-outbox-jdbc-postgresql` 의 `JdbcOutboxRepository` 는 스프링 스테레오타입도 `@Bean` 선언도 없고, `new JdbcOutboxRepository` 가 main 에 0 건이다.
|
||||
|
||||
## JdbcOutboxRepository 참조 위치
|
||||
|
||||
:::evidence key="messaging-spring-boot-starter-f02" alt="코드베이스에서 JdbcOutboxRepository 를 검색한 출력 5줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="JdbcOutboxRepository 코드베이스 검색 — 5줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## 그래서 발신함 경로만 조용히 빠진다
|
||||
|
||||
이 스타터를 켠 배포는 발행 경로는 얻고 발신함 경로는 얻지 못하며, 그 사실이 시작 시점에 어떤 신호도 내지 않는다.
|
||||
|
||||
## 사슬이 자기 클래스 안을 가리킨다
|
||||
|
||||
둘째와 셋째가 같은 설정 클래스 안에서 방금 선언된 빈의 존재를 조건으로 삼는다. 스프링은 `@ConditionalOnBean` 을 자동 설정 클래스에서만, 그리고 등록 순서에 의존하는 방식으로만 신뢰할 수 있다고 문서화한다. 지금은 첫 조건이 이미 거짓이라 결과가 드러나지 않고, 발신함을 배선하는 순간 이 사슬이 실제로 평가된다.
|
||||
|
||||
## 같은 가족의 다른 결정과 대비된다
|
||||
|
||||
관리 평면은 스위치가 켜졌을 때 만들어지지 **않는** 타입의 부재를 javadoc 에 명시한다(`DestructiveMessagingAdmin` 하나). 이쪽은 여섯이 조용히 빠진다. 수정은 둘이다 — 발신함을 요구하는 설정에서 저장소 빈이 없으면 시작을 거부하는 검증(`StartupProfileValidation` 형태), 그리고 중계·작업자·수명을 하나의 `@Bean` 으로 합치거나 조건을 전부 최초 두 타입으로 표현하는 것.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
애플리케이션이 저장소 빈을 공급한 상태로 컨텍스트를 세우지 않았다. 저장소에 그런 애플리케이션이 없다.
|
||||
|
||||
<!-- body:end -->
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: messaging-spring-boot-starter-f04
|
||||
title: 설정 경로의 재시도가 예외 분류를 표현할 수 없다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:messaging-spring-boot-starter-f04
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: messaging-spring-boot-starter-f04
|
||||
file: ../../../final/evidence/rendered/messaging-spring-boot-starter-f04.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/messaging-spring-boot-starter-f04.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/messaging/messaging-spring-boot-starter.md#L342 이다.
|
||||
module: messaging-spring-boot-starter
|
||||
priority: P3
|
||||
---
|
||||
|
||||
# 설정 경로의 재시도가 예외 분류를 표현할 수 없다
|
||||
|
||||
RetryPolicy 는 성분 열이고 그중 둘이 분류 집합이다. DestinationSettings.Retry 에는 이 둘에 대응하는 키가 없고, 컴파일러가 상수로 채운다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **검증기는 발행이 아니라 주입이 강제다**
|
||||
같은 분석 리프에서 끌어낸 규칙이다.
|
||||
|
||||
## 문제
|
||||
|
||||
RetryPolicy 는 성분 열이고 그중 둘이 분류 집합이다.
|
||||
|
||||
DestinationSettings.Retry 에는 이 둘에 대응하는 키가 없고, 컴파일러가 상수로 채운다.
|
||||
|
||||
## 결론
|
||||
|
||||
빈 집합은 "기본 분류 그대로" 라는 중립값이므로 오동작은 아니다.
|
||||
|
||||
문제는 비대칭이다.
|
||||
|
||||
DestinationProfile 을 자바로 선언한 배포는 두 집합을 조정할 수 있고, 문서대로 YAML 로 설정한 배포는 할 수 없다.
|
||||
|
||||
이 리프가 반복해서 근거로 든 규칙이 정확히 그 비대칭을 금지한다.
|
||||
|
||||
수정은 Retry 에 두 키를 더하는 것이다.
|
||||
|
||||
FailureCategory 는 열거이므로 relaxed binding 이 그대로 처리한다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12 java -version 으로 확인
|
||||
Gradle : 9.0.0 src/gradle/wrapper/gradle-wrapper.properties 의 distributionUrl 로 확인
|
||||
확인 방식 : RetryPolicy 참조 36건 검색과 설정 record 의 키 목록 대조
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 analysis/messaging/messaging-spring-boot-starter.md#L342 에 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
`RetryPolicy` 는 성분 열이고 그중 둘이 분류 집합이다. `DestinationSettings.Retry` 에는 이 둘에 대응하는 키가 없고, 컴파일러가 상수로 채운다.
|
||||
|
||||
## RetryPolicy 참조 위치
|
||||
|
||||
:::evidence key="messaging-spring-boot-starter-f04" alt="코드베이스에서 RetryPolicy 를 검색한 출력 36줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="RetryPolicy 코드베이스 검색 — 36줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## 오동작은 아니다
|
||||
|
||||
빈 집합은 "기본 분류 그대로" 라는 중립값이다.
|
||||
|
||||
## 문제는 비대칭이다
|
||||
|
||||
`DestinationProfile` 을 자바로 선언한 배포는 두 집합을 조정할 수 있고, 문서대로 YAML 로 설정한 배포는 할 수 없다. 이 리프가 반복해서 근거로 든 규칙이 정확히 그 비대칭을 금지한다. 수정은 `Retry` 에 두 키를 더하는 것이다 — `FailureCategory` 는 열거이므로 relaxed binding 이 그대로 처리한다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
설정으로 분류를 지정해 무시되는 것을 실행으로 확인하지 않았다. 대응 키가 없어 컴파일러가 상수로 채운다는 것으로 판정했다.
|
||||
|
||||
<!-- body:end -->
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: messaging-testkit-f02
|
||||
title: 클래스 javadoc 이 강제되지 않는 규칙을 강제된다고 말한다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:messaging-testkit-f02
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: messaging-testkit-f02
|
||||
file: ../../../final/evidence/rendered/messaging-testkit-f02.svg
|
||||
- key: messaging-testkit-f02-diagram
|
||||
file: ../../../final/assets/diagrams/messaging-testkit-f02.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/messaging-testkit-f02.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/messaging/messaging-testkit.md#L950 이다.
|
||||
module: messaging-testkit
|
||||
priority: P2
|
||||
---
|
||||
|
||||
# 클래스 javadoc 이 강제되지 않는 규칙을 강제된다고 말한다
|
||||
|
||||
BrokerFailureMatrix.java:18-20 이 "A Stable adapter must cover every scenario. That rule is enforced by a test rather than documented" 라고 쓰고 있으나, isComplete 를 Stable 어댑터에 거는 테스트는 없다(EVD-300).
|
||||
|
||||
## 문제
|
||||
|
||||
BrokerFailureMatrix.java:18-20 이 "A Stable adapter must cover every scenario.
|
||||
|
||||
That rule is enforced by a test rather than documented" 라고 쓰고 있으나, isComplete 를 Stable 어댑터에 거는 테스트는 없다(EVD-300).
|
||||
|
||||
## 결론
|
||||
|
||||
유일한 호출부는 Experimental 어댑터가 불완전함을 단언한다.
|
||||
|
||||
실제 Stable 인 messaging-kafka 는 connection-refused gap 을 가진 채 통과하며, 그 gap 은 같은 모듈이 명시적으로 단언한다.
|
||||
|
||||
코드 쪽 결정("gap 을 열거하되 비어 있음을 단언하지 않는다")은 옳고, 그 이유도 CrossBrokerContractSuite.java:45-47 에 적혀 있다.
|
||||
|
||||
문제는 javadoc 이 갱신되지 않은 것이다.
|
||||
|
||||
이 리프의 다른 javadoc 여섯 곳이 자기 이력을 정확히 남긴 것과 대비되어 더 눈에 띈다.
|
||||
|
||||
같은 이유로 테스트 메서드 이름 everyStableAdapterCoversEveryFaultScenario 도 본문과 맞지 않는다.
|
||||
|
||||
everyStableAdaptersGapsAreExactlyWhatTheEvidenceShows 같은 이름이 본문을 정확히 기술한다.
|
||||
|
||||
수정 방향: javadoc 을 현재 규칙("Stable 은 live-broker 증거를 하나 이상 요구한다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12 java -version 으로 확인
|
||||
Gradle : 9.0.0 src/gradle/wrapper/gradle-wrapper.properties 의 distributionUrl 로 확인
|
||||
확인 방식 : isComplete 호출부 1건 확인과 그 호출이 Stable 어댑터를 대상으로 하는지 대조
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 analysis/messaging/messaging-testkit.md#L950 에 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
`BrokerFailureMatrix.java:18-20` 이 "A Stable adapter must cover every scenario. That rule is enforced by a test rather than documented" 라고 쓰고 있으나, `isComplete` 를 Stable 어댑터에 거는 테스트는 없다(`EVD-300`).
|
||||
|
||||
## 강제가 없는 규칙
|
||||
|
||||
:::evidence key="messaging-testkit-f02-diagram" alt="클래스 javadoc 만 규칙을 표현하는 것 안에 놓이고 Stable 에 거는 테스트가 바깥에 빗금으로 놓인다" caption="강제가 없는 규칙" zoom="false"
|
||||
:::
|
||||
|
||||
유일한 호출부는 Experimental 어댑터가 불완전함을 단언한다. 실제 Stable 인 `messaging-kafka` 는 `connection-refused` gap 을 가진 채 통과하며, 그 gap 은 같은 모듈이 명시적으로 단언한다.
|
||||
|
||||
## javadoc 이 강제된다고 말한 규칙
|
||||
|
||||
:::evidence key="messaging-testkit-f02" alt="분석 문서 analysis/messaging/messaging-testkit.md 에서 이 기록의 근거 절을 그대로 잘라낸 15줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="analysis/messaging/messaging-testkit.md 발췌 — 15줄" zoom="true"
|
||||
:::
|
||||
|
||||
## 코드 쪽 결정은 옳다
|
||||
|
||||
"gap 을 열거하되 비어 있음을 단언하지 않는다" 이고 그 이유도 `CrossBrokerContractSuite.java:45-47` 에 적혀 있다. 문제는 **javadoc 이 갱신되지 않은 것**이다. 이 리프의 다른 javadoc 여섯 곳이 자기 이력을 정확히 남긴 것과 대비되어 더 눈에 띈다.
|
||||
|
||||
## 테스트 이름도 본문과 맞지 않는다
|
||||
|
||||
`everyStableAdapterCoversEveryFaultScenario` 대신 `everyStableAdaptersGapsAreExactlyWhatTheEvidenceShows` 같은 이름이 본문을 정확히 기술한다. 수정 방향은 javadoc 을 현재 규칙("Stable 은 live-broker 증거를 하나 이상 요구한다. 전 시나리오 커버리지는 목표이지 게이트가 아니며, gap 은 `knownGaps` 로 명명된다")으로 바꾸는 것이다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
매니페스트를 손으로 고쳐 게이트가 실패하는 것은 확인하지 않았다. 그것은 애플리케이션 소스 수정에 해당해 하지 않았다.
|
||||
|
||||
<!-- body:end -->
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: messaging-testkit-f08
|
||||
title: gitCommit 은 기록되지만 읽혀 판정되지 않는다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:messaging-testkit-f08
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: messaging-testkit-f08
|
||||
file: ../../../final/evidence/rendered/messaging-testkit-f08.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/messaging-testkit-f08.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/messaging/messaging-testkit.md#L984 이다.
|
||||
module: messaging-testkit
|
||||
priority: P3
|
||||
---
|
||||
|
||||
# gitCommit 은 기록되지만 읽혀 판정되지 않는다
|
||||
|
||||
BrokerCertificationEvidence javadoc 이 "without them the evidence cannot be checked against anything later" 라고 쓰지만, 실제로 gitCommit 을 읽어 무언가를 결정하는 코드는 없고 게이트는 오히려 그 필드를 비교에서 제외한다(§12.4(c)). 현재 매니페스트의 커밋은 HEAD 가 아니다(e98b56eb vs 21234e38).
|
||||
|
||||
## 문제
|
||||
|
||||
BrokerCertificationEvidence javadoc 이 "without them the evidence cannot be checked against anything later" 라고 쓰지만, 실제로 gitCommit 을 읽어 무언가를 결정하는 코드는 없고 게이트는 오히려 그 필드를 비교에서 제외한다(§12.4(c)).
|
||||
|
||||
현재 매니페스트의 커밋은 HEAD 가 아니다(e98b56eb vs 21234e38).
|
||||
|
||||
## 결론
|
||||
|
||||
"증거가 얼마나 오래된 트리에서 나왔는가" 를 보고하는 것은 유용한 진단이 될 수 있다 — 게이트로 만들 필요는 없고, knownGaps 처럼 사실로 노출하면 이 리프의 나머지 설계와 결이 맞는다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12 java -version 으로 확인
|
||||
Gradle : 9.0.0 src/gradle/wrapper/gradle-wrapper.properties 의 distributionUrl 로 확인
|
||||
확인 방식 : BrokerCertificationEvidence 참조 26건 검색과 게이트가 비교에서 제외하는 필드 확인
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 analysis/messaging/messaging-testkit.md#L984 에 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
`BrokerCertificationEvidence` javadoc 이 "without them the evidence cannot be checked against anything later" 라고 쓰지만, 실제로 `gitCommit` 을 읽어 무언가를 결정하는 코드는 없고 게이트는 오히려 그 필드를 비교에서 제외한다(§12.4(c)).
|
||||
|
||||
## BrokerCertificationEvidence 참조 위치
|
||||
|
||||
:::evidence key="messaging-testkit-f08" alt="코드베이스에서 BrokerCertificationEvidence 를 검색한 출력 26줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="BrokerCertificationEvidence 코드베이스 검색 — 26줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## 현재 매니페스트의 커밋은 HEAD 가 아니다
|
||||
|
||||
`e98b56eb` vs `21234e38`.
|
||||
|
||||
## 게이트로 만들 필요는 없다
|
||||
|
||||
"증거가 얼마나 오래된 트리에서 나왔는가" 를 보고하는 것은 유용한 진단이 될 수 있다. `knownGaps` 처럼 사실로 노출하면 이 리프의 나머지 설계와 결이 맞는다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
게이트를 다른 커밋의 매니페스트로 돌려 보지 않았다. 비교 대상 필드 목록으로 판정했다.
|
||||
|
||||
<!-- body:end -->
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
---
|
||||
kind: QUESTION
|
||||
slug: messaging-inbox-jdbc-postgresql-f04
|
||||
title: 세 갈래 판정이 포트의 boolean에서 두 갈래로 접힌다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: open-question:messaging-inbox-jdbc-postgresql-f04
|
||||
questionStatus: OPEN
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
source:
|
||||
- analysis/messaging/messaging-inbox-jdbc-postgresql.md#L675
|
||||
---
|
||||
|
||||
# 세 갈래 판정이 포트의 boolean에서 두 갈래로 접힌다
|
||||
|
||||
같은 `false` 가 "이미 처리됐다" 와 "다른 인스턴스가 처리 중이다" 를 함께 뜻한다. 후자가 실제로 도달 가능한 상태인지에 따라 이것이 결함인지 과설계인지가 갈린다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **컬럼 폭은 애플리케이션 검증과 짝을 이룬다**
|
||||
같은 분석 리프에서 끌어낸 규칙이다.
|
||||
- **같은 안전 규칙은 한 공식과 한 강제 시점을 갖는다**
|
||||
같은 분석 리프에서 끌어낸 규칙이다.
|
||||
|
||||
## 사실
|
||||
|
||||
InboxResult 가 세 값과 isSafeToSettle() 을 갖는데 production 은 APPLIED 만 만든다.
|
||||
|
||||
InboxRepository.reserve 가 boolean 을 반환하므로 ALREADY_APPLIED 와 CLAIMED_ELSEWHERE 가 같은 false 로 들어온다. TransactionalInboxHandler 는 그 경우 HandleResult.success() 를 반환한다 — 정산한다.
|
||||
|
||||
InboxResult javadoc 이 세 값이 필요한 이유로 정확히 그 정산을 든다 — "would settle a message whose effect is still only half-written by another instance".
|
||||
|
||||
## 미지수
|
||||
|
||||
ON CONFLICT DO NOTHING 이 미커밋 충돌에 대해 대기하는가 즉시 0을 반환하는가. 대기하면 CLAIMED_ELSEWHERE 는 도달 불가능한 상태이고 enum 이 과설계인 것이며, 즉시 0을 반환하면 이것은 실제 결함이다.
|
||||
|
||||
## 선택지
|
||||
|
||||
포트 반환 타입을 InboxResult 로 바꾼다
|
||||
세 갈래가 호출자까지 도달하고 정산 판단이 갈린다. 확인 결과 발생 가능할 때의 선택이다.
|
||||
|
||||
현 형태를 유지하고 도달 불가임을 적는다
|
||||
대기가 확인되면 enum 의 세 번째 값이 왜 남아 있는지가 기록돼야 한다.
|
||||
|
||||
## 다음 검증
|
||||
|
||||
두 커넥션에서 같은 (message, consumer) 를 예약하고 한쪽을 커밋하지 않은 채 다른 쪽의 executeUpdate() 반환을 관측한다. InboxPostgresIT 에 추가 가능하다.
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
---
|
||||
kind: REFERENCE
|
||||
slug: messaging-cloudevents-f05
|
||||
title: 문서가 범위를 제한하면 그 제한을 누가 강제하는지 같이 적는다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: reference:messaging-cloudevents-f05
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
source:
|
||||
- analysis/messaging/messaging-cloudevents.md#L556
|
||||
---
|
||||
|
||||
# 문서가 범위를 제한하면 그 제한을 누가 강제하는지 같이 적는다
|
||||
|
||||
## 관계
|
||||
|
||||
- **배포 아티팩트가 싣지만 아무도 부르지 않는다**
|
||||
이 규칙의 근거는 같은 분석 리프의 판정이 소유한다.
|
||||
- **상호운용을 위한 매퍼가 명세 준수 이벤트를 분류되지 않은 예외로 거절한다**
|
||||
이 규칙의 근거는 같은 분석 리프의 판정이 소유한다.
|
||||
|
||||
## 목적
|
||||
|
||||
javadoc 이 적용 범위를 좁혀 놓고 코드에 그 분기가 없으면, 제한은 읽는 사람의 기억에만 존재한다. 지금은 소비자가 0 이라 무해하고, 배선되는 순간 범위 밖 봉투가 그대로 통과한다.
|
||||
|
||||
## 규칙
|
||||
|
||||
1. 범위를 제한하는 문장을 찾으면 그 옆에 강제 주체를 적는다
|
||||
CloudEventMapper 는 "Offered for domain and integration events only. Commands and work items are not forced through CloudEvents." 라고 적는다.
|
||||
|
||||
2. 코드에 그 분기가 있는지 확인한다
|
||||
DefaultCloudEventMapper 전문에 DestinationKind 를 보는 분기가 없다.
|
||||
|
||||
3. 강제하지 않기로 했다면 호출자 책임임을 명시한다
|
||||
강제할 것이면 toCloudEvent 가 DestinationKind 를 받아 검사한다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
javadoc·README·계약 문서가 적용 대상을 열거하는 모든 자리.
|
||||
|
||||
## 예외
|
||||
|
||||
호출 지점이 하나뿐이고 그 호출자가 범위를 이미 좁히는 경우는 예외다. 이 매퍼는 호출자가 0 이라 그 예외에 해당하지 않는다.
|
||||
|
||||
## 예시
|
||||
|
||||
CloudEventMapper.java:11-13 의 범위 진술과, git grep -n 'DestinationKind' -- 'src/messaging/messaging-cloudevents/**' 가 매치를 돌려주지 않는다는 것.
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
---
|
||||
kind: REFERENCE
|
||||
slug: messaging-observability-f06
|
||||
title: 타입이 문서화한 불변식은 타입이 강제한다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: reference:messaging-observability-f06
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
source:
|
||||
- analysis/messaging/messaging-observability.md#L717
|
||||
---
|
||||
|
||||
# 타입이 문서화한 불변식은 타입이 강제한다
|
||||
|
||||
## 관계
|
||||
|
||||
- **`extract`가 손상된 추적 헤더에 분류되지 않은 예외를 던진다**
|
||||
이 규칙의 근거는 같은 분석 리프의 판정이 소유한다.
|
||||
- **관측 구현이 조립되지 않고, 그 재료 둘만 bean으로 존재한다**
|
||||
이 규칙의 근거는 같은 분석 리프의 판정이 소유한다.
|
||||
- **감사 sink 인터페이스가 사용처에서 다시 선언된다**
|
||||
이 규칙의 근거는 같은 분석 리프의 판정이 소유한다.
|
||||
- **자격증명 판정이 core-api보다 약하다**
|
||||
이 규칙의 근거는 같은 분석 리프의 판정이 소유한다.
|
||||
- **태그 어휘가 존재하고 유일한 호출부가 우회해, 실패 분류가 기록되지 않는다**
|
||||
이 규칙의 근거는 같은 분석 리프의 판정이 소유한다.
|
||||
|
||||
## 목적
|
||||
|
||||
javadoc 이 "the details are passed through MessagingRedactor" 라고 적는데 생성자는 Map.copyOf 만 한다. 감사 기록은 "often retained far longer than the source topic" 이고 운영자가 읽는다. redaction 이 호출자 책임이면 새 호출부가 그것을 잊는 순간 민감한 값이 가장 오래 남는 곳에 들어간다.
|
||||
|
||||
## 규칙
|
||||
|
||||
1. javadoc 이 서술하는 불변식을 생성자 본문과 대조한다
|
||||
MessagingAuditEvent.java:30-38 이 그 대조 지점이다.
|
||||
|
||||
2. 강제하지 않으면 문장을 호출자 책임으로 고친다
|
||||
둘 중 하나만 참일 수 있다.
|
||||
|
||||
3. 같은 가족의 강제 사례를 기준으로 삼는다
|
||||
messaging-core-api 의 FailureDescriptor 는 512자 절단을 생성자에서 한다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
javadoc 이 값의 형태·상한·정제 여부를 단정하는 모든 record·값 객체.
|
||||
|
||||
## 예외
|
||||
|
||||
호출 지점이 전부 한 파일 안에 있고 그 파일이 불변식을 지키는 것을 테스트가 붙드는 경우는 예외로 볼 수 있다. 여기서는 RedriveService:126 과 ReplayService:73 이 redactor 를 부르는지부터 확인해야 한다.
|
||||
|
||||
## 예시
|
||||
|
||||
MessagingAuditEvent.java:30-38 의 생성자 본문과 그 javadoc 문장.
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
---
|
||||
kind: REFERENCE
|
||||
slug: messaging-policy-f06
|
||||
title: 구성 오류는 한 예외 타입과 안정 코드로 보고한다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: reference:messaging-policy-f06
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
source:
|
||||
- analysis/messaging/messaging-policy.md#L826
|
||||
---
|
||||
|
||||
# 구성 오류는 한 예외 타입과 안정 코드로 보고한다
|
||||
|
||||
## 관계
|
||||
|
||||
- **재시도 엔진과 DLQ 조정자가 bean으로 만들어지고 주입되는 곳이 없다**
|
||||
이 규칙의 근거는 같은 분석 리프의 판정이 소유한다.
|
||||
- **재시도와 DLQ 각각에 두 개의 구현이 있고 정본이 표시되지 않았다**
|
||||
이 규칙의 근거는 같은 분석 리프의 판정이 소유한다.
|
||||
- **DLQ 메타데이터의 두 시각이 항상 같다**
|
||||
이 규칙의 근거는 같은 분석 리프의 판정이 소유한다.
|
||||
|
||||
## 목적
|
||||
|
||||
부팅 실패라서 실무 영향은 낮지만, FailureDescriptor 가 없으면 그 거절에 코드도 카테고리도 붙지 않는다. 같은 leaf 안에서 구성 오류가 두 방식으로 보고되면 운영자가 받는 신호가 규칙마다 달라진다.
|
||||
|
||||
## 규칙
|
||||
|
||||
1. 한 leaf 안의 구성 거절이 같은 예외 타입을 쓰는지 센다
|
||||
DestinationProfileValidator 의 거절 16개가 전부 IllegalArgumentException 이다. 같은 leaf 의 DeadLetterOrchestrator 는 MessagingConfigurationException("DEAD_LETTER_NOT_CONFIGURED") 을 쓴다.
|
||||
|
||||
2. 플랫폼 예외가 이미 있는지 확인한다
|
||||
MessagingConfigurationException 의 javadoc 이 "Raised at startup wherever possible" 이라고 적는다. 자리를 비워 둔 것이 아니라 이미 그 용도로 선언돼 있다.
|
||||
|
||||
3. 규칙마다 안정 코드를 준다
|
||||
16개 규칙이 하나의 예외 타입을 공유하더라도 코드가 갈라져야 어느 규칙이 걸렸는지 읽힌다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
시작 시점에 설정을 거절하는 검증기가 여러 개 있고 그것들이 한 leaf 를 공유하는 자리.
|
||||
|
||||
## 예외
|
||||
|
||||
SSOT 가 이 규칙의 반례를 적지 않았다. 표준 예외를 유지할 근거가 있다면 그것이 javadoc 에 있어야 하는데, 16개 거절 어디에도 없다.
|
||||
|
||||
## 예시
|
||||
|
||||
DestinationProfileValidator 전문의 throw 문과 MessagingConfigurationException 의 javadoc. 확인 방법은 두 클래스의 throw 문을 대조하는 것이다.
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
---
|
||||
kind: REFERENCE
|
||||
slug: messaging-policy-f07
|
||||
title: 저장소 밖 문서를 절 번호로 인용하지 않는다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: reference:messaging-policy-f07
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
source:
|
||||
- analysis/messaging/messaging-policy.md#L835
|
||||
---
|
||||
|
||||
# 저장소 밖 문서를 절 번호로 인용하지 않는다
|
||||
|
||||
## 관계
|
||||
|
||||
- **재시도 엔진과 DLQ 조정자가 bean으로 만들어지고 주입되는 곳이 없다**
|
||||
이 규칙의 근거는 같은 분석 리프의 판정이 소유한다.
|
||||
- **재시도와 DLQ 각각에 두 개의 구현이 있고 정본이 표시되지 않았다**
|
||||
이 규칙의 근거는 같은 분석 리프의 판정이 소유한다.
|
||||
- **DLQ 메타데이터의 두 시각이 항상 같다**
|
||||
이 규칙의 근거는 같은 분석 리프의 판정이 소유한다.
|
||||
|
||||
## 목적
|
||||
|
||||
인용된 내용이 코드와 일치해도, 근거를 확인하려는 사람이 그 문서에 도달할 수 없으면 인용은 검증 불가능한 권위가 된다.
|
||||
|
||||
## 규칙
|
||||
|
||||
1. javadoc 의 문서 인용이 저장소 안에서 해소되는지 확인한다
|
||||
InFlightLimiter javadoc 이 "Section 40.3 of the design specifies 'bounded wait, then MessageBackpressureException'" 이라고 적는다. 그 절 번호를 가진 문서를 저장소에서 찾지 못했다.
|
||||
|
||||
2. 해소되지 않으면 절 번호를 빼고 인용문만 남긴다
|
||||
인용문 자체는 코드와 일치하므로 내용 drift 가 아니다. 문제는 좌표다.
|
||||
|
||||
3. 실제 문서가 있으면 그 경로로 바꾼다
|
||||
경로는 검색으로 확인 가능하고 절 번호는 아니다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
javadoc·주석·README 가 외부 설계 문서를 좌표로 인용하는 모든 자리.
|
||||
|
||||
## 예외
|
||||
|
||||
저장소에 함께 커밋된 문서의 절 번호는 이 규칙의 대상이 아니다. 그 좌표는 같은 리비전 안에서 해소된다.
|
||||
|
||||
## 예시
|
||||
|
||||
InFlightLimiter.java:11-13 의 인용문과, docs/messaging/*.md 10개 및 계획 문서에 절 40.3 이 없다는 것. 확인 방법은 git grep -n '40\.3' -- docs 다.
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
---
|
||||
kind: REFERENCE
|
||||
slug: messaging-reliability-api-f06
|
||||
title: 호출 컨텍스트가 계약이면 그 컨텍스트를 검증할 수단을 함께 정한다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: reference:messaging-reliability-api-f06
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
source:
|
||||
- analysis/messaging/messaging-reliability-api.md#L734
|
||||
---
|
||||
|
||||
# 호출 컨텍스트가 계약이면 그 컨텍스트를 검증할 수단을 함께 정한다
|
||||
|
||||
## 관계
|
||||
|
||||
- **inbox 보존 규칙이 문서로만 있다**
|
||||
이 규칙의 근거는 같은 분석 리프의 판정이 소유한다.
|
||||
- **포트가 bounded/unbounded purge 두 오버로드를 나란히 노출하고, 호출자가 무제한 쪽을 고른다**
|
||||
이 규칙의 근거는 같은 분석 리프의 판정이 소유한다.
|
||||
- **fencing token 경로가 실제 데이터베이스에 대해 실행되지 않는다**
|
||||
이 규칙의 근거는 같은 분석 리프의 판정이 소유한다.
|
||||
- **한 인터페이스가 같은 전이의 두 세대를 갖고, 안전하지 않은 쪽에 `@Deprecated`가 없다**
|
||||
이 규칙의 근거는 같은 분석 리프의 판정이 소유한다.
|
||||
|
||||
## 목적
|
||||
|
||||
위반이 조용하다는 것이 이 계약들의 특징이다. InboxRepository.reserve 를 별도 트랜잭션에서 부르면 "exactly the gap the Inbox exists to close" 가 다시 열리는데, 그 순간 어떤 예외도 나지 않는다.
|
||||
|
||||
## 규칙
|
||||
|
||||
1. javadoc 이 요구하는 호출 컨텍스트를 목록으로 만든다
|
||||
셋이다. OutboxRepository.append 는 호출자 트랜잭션 안, InboxRepository.reserve 는 부작용과 같은 트랜잭션, TransactionalMessageAction 은 자기 트랜잭션을 시작하지 않을 것.
|
||||
|
||||
2. 타입으로 표현된 부분과 문장으로만 남은 부분을 가른다
|
||||
ReliableMessagePublisher 는 void 반환으로 계약의 일부를 타입에 담았다 — "Handing back a PublishResult here would be a lie". 나머지 셋에는 그런 장치가 없다.
|
||||
|
||||
3. 타입으로 못 담으면 검증 수단을 정한다
|
||||
구현 leaf 가 트랜잭션 참여를 검증하는 테스트를 두거나, ArchUnit 으로 append 와 reserve 호출부의 트랜잭션 컨텍스트를 검사한다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
포트 javadoc 이 호출자 쪽 조건을 요구하는 모든 인터페이스. 트랜잭션 참여·락 보유·스레드 소속이 대표적이다.
|
||||
|
||||
## 예외
|
||||
|
||||
반환 타입이나 파라미터로 컨텍스트를 강제할 수 있으면 별도 검증이 필요 없다. ReliableMessagePublisher 의 void 가 그 경우다.
|
||||
|
||||
## 예시
|
||||
|
||||
세 javadoc 의 요구 문장. 확인 방법은 그 셋과 구현의 @Transactional 배치를 대조하는 것이고, 그 대조는 구현 leaf 가 소유한다.
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
---
|
||||
kind: REFERENCE
|
||||
slug: messaging-schema-api-f02
|
||||
title: port 계약은 동시성 요구를 적는다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: reference:messaging-schema-api-f02
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
source:
|
||||
- analysis/messaging/messaging-schema-api.md#L503
|
||||
---
|
||||
|
||||
# port 계약은 동시성 요구를 적는다
|
||||
|
||||
## 관계
|
||||
|
||||
- **포맷 독립 진화 규칙이 호출되지 않고, 그것이 막으려던 중복이 실제로 생겼다**
|
||||
이 규칙의 근거는 같은 분석 리프의 판정이 소유한다.
|
||||
|
||||
## 목적
|
||||
|
||||
MessageCodecRegistry 의 유일한 구현 RegisteredMessageCodecs 는 Map.copyOf 로 불변이라 안전하다. 그것은 구현의 성질이지 계약이 아니다. 외부 registry 를 감싸는 SchemaRegistry 구현은 브로커 소비자 스레드들에서 동시에 호출된다.
|
||||
|
||||
## 규칙
|
||||
|
||||
1. port javadoc 에 동시성 문장이 있는지 센다
|
||||
SchemaRegistry 와 MessageCodecRegistry 에는 없다. BoundedByteSink 만 "not thread-safe" 를 명시한다.
|
||||
|
||||
2. 현재 구현이 안전하다는 사실과 계약을 구분한다
|
||||
구현이 하나뿐이라 안전한 것과 계약이 안전을 요구하는 것은 다르다.
|
||||
|
||||
3. 요구를 문장으로 적는다
|
||||
port javadoc 에 "구현은 스레드 안전해야 한다" 를 명시한다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
구현체가 다른 leaf 나 파생 프로젝트에서 만들어질 수 있는 모든 port.
|
||||
|
||||
## 예외
|
||||
|
||||
호출이 단일 스레드에 갇혀 있음을 타입이 보장하는 경우는 대상이 아니다. BoundedByteSink 는 그 성질을 명시해 이 규칙을 이미 지킨 쪽이다.
|
||||
|
||||
## 예시
|
||||
|
||||
세 타입의 javadoc 전문. 확인 방법은 그 셋을 나란히 읽는 것이다.
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
---
|
||||
kind: REFERENCE
|
||||
slug: messaging-schema-api-f03
|
||||
title: 도달성 판정은 단어가 아니라 import로 확인한다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: reference:messaging-schema-api-f03
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
source:
|
||||
- analysis/messaging/messaging-schema-api.md#L512
|
||||
---
|
||||
|
||||
# 도달성 판정은 단어가 아니라 import로 확인한다
|
||||
|
||||
## 관계
|
||||
|
||||
- **포맷 독립 진화 규칙이 호출되지 않고, 그것이 막으려던 중복이 실제로 생겼다**
|
||||
이 규칙의 근거는 같은 분석 리프의 판정이 소유한다.
|
||||
|
||||
## 목적
|
||||
|
||||
이 저장소에서 실제로 오탐이 났다. 단어 검색이 SchemaRegistry 2건을 맞췄고 둘 다 다른 타입이었다. 사람이 같은 실수를 한다.
|
||||
|
||||
## 규칙
|
||||
|
||||
1. 같은 단순 이름이 여러 패키지에 있는지 먼저 확인한다
|
||||
dev.caskeleton.messaging.schema.SchemaRegistry(이 leaf 의 port)와 com.networknt.schema.SchemaRegistry(JSON Schema 라이브러리)가 공존하고, 실제로 import 되는 것은 후자뿐이다.
|
||||
|
||||
2. 판정은 import 문으로 한다
|
||||
git grep -n 'import .*\.SchemaRegistry;' -- src 가 그 판정을 준다.
|
||||
|
||||
3. 이름 충돌 자체는 고치지 않아도 된다
|
||||
충돌을 없애는 것보다 판정 방법을 고정하는 것이 싸다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
타입 참조 수를 세어 도달성·미사용을 판정하는 모든 조사.
|
||||
|
||||
## 예외
|
||||
|
||||
단순 이름이 저장소 안에서 유일하다고 확인된 경우에는 단어 검색으로 충분하다. 그 확인 자체가 이 규칙의 첫 단계다.
|
||||
|
||||
## 예시
|
||||
|
||||
evidence/raw/272 §C 의 검색 결과와 두 패키지의 공존.
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
---
|
||||
kind: REFERENCE
|
||||
slug: messaging-spring-cloud-stream-bridge-f03
|
||||
title: 한 개념의 등록 상태를 두 객체가 나눠 갖지 않는다
|
||||
topic: declaration-and-document-drift
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: reference:messaging-spring-cloud-stream-bridge-f03
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
source:
|
||||
- analysis/messaging/messaging-spring-cloud-stream-bridge.md#L570
|
||||
---
|
||||
|
||||
# 한 개념의 등록 상태를 두 객체가 나눠 갖지 않는다
|
||||
|
||||
## 목적
|
||||
|
||||
한 바인딩에 대해 두 객체가 각자 등록을 갖고 서로를 모른다. bindConsumer 를 부르고 register 를 부르지 않으면 publisher 쪽은 바인딩이 있다고 보고하고 실제 전달은 NO_BRIDGED_HANDLER 로 실패한다. consumerBinding(dest) 는 그 불일치를 드러내지 않는다.
|
||||
|
||||
## 규칙
|
||||
|
||||
1. 인터페이스가 선언한 등록 메서드를 누가 구현하는지 본다
|
||||
MessagingBindingBridge 가 bindPublisher 와 bindConsumer 둘을 선언한다. SpringCloudStreamPublisherBridge 가 둘 다 구현하고 inputBindings 맵에 기록한다.
|
||||
|
||||
2. 같은 개념의 상태가 다른 객체에도 있는지 본다
|
||||
SpringCloudStreamConsumerBridge 는 이 인터페이스를 구현하지 않고 자기 handlers 와 destinations 맵에 기록한다.
|
||||
|
||||
3. 상태를 한쪽으로 모으거나 인터페이스를 나눈다
|
||||
consumer bridge 가 MessagingBindingBridge 를 구현하고 publisher 가 bindConsumer 를 위임하거나, 인터페이스를 발행과 수신으로 나눈다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
등록 API 가 인터페이스로 선언되고 그 인터페이스를 일부 구현체만 구현하는 자리.
|
||||
|
||||
## 예외
|
||||
|
||||
SSOT 가 이 규칙의 반례를 적지 않았다. 두 상태가 의도적으로 독립이라면 조회 API 가 그 독립을 드러내야 하는데, consumerBinding(dest) 는 드러내지 않는다.
|
||||
|
||||
## 예시
|
||||
|
||||
evidence/raw/296 §C. 확인 방법은 두 클래스의 필드와 인터페이스 구현을 보는 것이다.
|
||||
|
||||
Reference in New Issue
Block a user