Files
document-haness/docs/clean-architecture-backend-template/tech-log-studio/multitenancy-isolation/case/case-analysis-finding-a05-f010.md
T
DongHyeonkaandClaude Opus 5 b2963105a8 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>
2026-09-04 22:51:59 +09:00

133 lines
9.6 KiB
Markdown

---
kind: CASE
slug: analysis-finding-a05-f010
title: 정본이라던 코디네이터는 그 포트를 구현하지 않고 다른 클래스가 구현한다
topic: multitenancy-isolation
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: case:analysis-finding-a05-f010
evidenceCapturedOn: 2026-09-04
body: case-analysis-finding-a05-f010.body.md
assets:
- key: analysis-finding-a05-f010
file: ../../../final/evidence/rendered/analysis-finding-a05-f010.svg
evidence:
- ../../../final/evidence/raw/analysis-finding-a05-f010.txt
source:
- 원본 분석 절은 analysis/05-adapter-outbound-persistence-jpa.md §10 이다.
---
# 정본이라던 코디네이터는 그 포트를 구현하지 않고 다른 클래스가 구현한다
`JpaTransactionAutoConfiguration:37` 주석이 정본 경계로 지목한 메서드는 `PolicyTransactionPort` 의 것인데, 같은 문장이 아래에 만들어지는 재시도 코디네이터로 이어진다. 그 포트를 실제로 구현하는 것은 어댑터 쪽 `SpringTransactionPort` 이고 코디네이터는 자기 실행기와 짝을 이룬다.
## 관계
- **중복 장치를 찾으면 어느 쪽이 조립됐는지 먼저 확인한다**
이 사례가 속한 구조다. 두 스택이 다 조립되고 어느 쪽이 정본인지가 갈린다.
- **재시도 코디네이터는 빈이지만 그것을 어디에도 적용하지 않는다**
같은 코디네이터의 배선 공백을 다룬 기록이다.
- **같은 개념의 두 어휘가 공존하면 하나를 죽은 것으로 표시한다**
이 사례가 어기는 규칙이다. 둘 다 살아 있고 어느 쪽도 죽은 것으로 표시되지 않았다.
## 문제
정본 트랜잭션 경계가 어디인지는 이 리프의 구조를 읽는 출발점이다.
자동설정 주석이 그 경계를 지목한다. 그 지목이 실제 구현 관계와 맞는지 확인했다.
## 결론
그 포트를 구현한다고 선언한 파일은 하나뿐이다. 없는 이름으로 같은 검색식을 걸어도 0 이 나오므로, 상위 인터페이스로 같은 검색을 걸어 21 을 받아 검색이 도는 것을 확인했다.
그 클래스가 자기 자바독에서 설명하는 것은 모드별 템플릿과 격리 수준 고정이다. 재시도라는 낱말이 거기 없다.
자동설정이 만드는 빈은 여섯이다 — CommitFailureClassifier, SpringJpaTransactionExecutor 둘, FullTransactionRetryCoordinator 둘, RetrySleeper. 그 목록에 포트 구현이 없다.
즉 주석이 한 문장 안에서 두 스택을 잇고 있다. 앞부분이 지목한 포트는 어댑터 쪽 클래스가 구현하고, 뒷부분이 설명하는 코디네이터는 자동설정 쪽 실행기와 짝을 이룬다.
네 이름의 main 참조 파일 수가 모두 4 개다. 어느 쪽도 죽어 있지 않다.
저장소 자신도 이것을 문제로 적어 두었다. docs/reviews/2026-08-14-jpa-module-code-review.md:119 가 트랜잭션 권위와 애플리케이션 계약이 두 벌이라는 것을 P1 로 매기고 PolicyTransactionPort 단일 facade 로 통합하자고 적는다.
동작이 틀리는 것은 아니다. 두 스택이 각각 돈다. 비용은 다음에 고치는 사람이 어느 쪽을 고쳐야 하는지 주석만으로는 알 수 없다는 데 있다.
## 검증 환경
OpenJDK : 21.0.12
확인 방식 : 자동설정 주석 인용, implements PolicyTransactionPort 검색을 없는 이름 자기시험과 대조 이름과 함께 계수, 구현체의 선언과 자바독 인용, 자동설정이 만드는 빈 목록 전수, 네 이름의 main 참조 파일 수 계수와 그중 하나의 파일 목록, 저장소 문서가 같은 것을 어떻게 적었는지 전수 검색
소스 수정 : x
## 재현 조건
1. 자동설정에서 정본 경계를 지목하는 주석을 인용한다.
2. 그 포트를 구현한다고 선언한 클래스를 찾고, 없는 이름과 대조 이름으로 같은 검색을 함께 건다.
3. 구현체의 선언과 클래스 자바독을 인용한다.
4. 자동설정이 만드는 빈을 전부 나열한다.
5. 두 스택의 이름 넷을 main 참조 파일 수로 센다.
6. 저장소 문서에서 그 포트 이름이 나오는 자리를 전부 찾는다.
## 본문
<!-- body:start -->
`JpaTransactionAutoConfiguration` 은 JPA 트랜잭션 스택을 조립한다. 그 파일의 주석이 정본 경계가 어디인지 적어 둔다.
## 주석이 지목한 경계와 실제 구현 관계
:::evidence key="analysis-finding-a05-f010" alt="저장소 루트에서 돌린 정적 검색 출력 84줄. 먼저 JpaTransactionAutoConfiguration 28~55번 줄이 실리는데 37번 줄 주석이 PolicyTransactionPort.inTransaction 을 정본 경계로 지목하고 아래의 재시도 코디네이터를 함께 설명한다. 이어서 PolicyTransactionPort 를 구현한다고 선언한 클래스가 SpringTransactionPort 31번 줄 하나로 나오고, 없는 이름으로 건 자기시험이 0 개, 대조로 센 implements TransactionPort 가 21 개다. 그 구현체의 선언과 자바독이 24~40번 줄로 실리는데 모드별로 미리 만든 트랜잭션 템플릿을 쓰고 모두 READ_COMMITTED 로 고정한다고 적는다. 그 아래에 자동설정이 만드는 빈들이 나오는데 CommitFailureClassifier 와 SpringJpaTransactionExecutor 둘과 FullTransactionRetryCoordinator 둘과 RetrySleeper 이고, 기본 최대 재시도 경과 시간이 30 초로 선언돼 있다. 두 스택을 참조하는 main 파일 수는 PolicyTransactionPort 와 FullTransactionRetryCoordinator 와 SpringJpaTransactionExecutor 와 SpringTransactionPort 가 각각 4 개이고, PolicyTransactionPort 를 참조하는 네 파일이 이름으로 나열된다. 마지막으로 문서 쪽 서술이 나오는데 코드 리뷰 문서가 두 벌의 트랜잭션 계약이 공존한다는 것을 P1 로 적고 PolicyTransactionPort 를 단일 facade 로 통합하자고 적는다." caption="자동설정 주석이 지목한 정본 경계 · 그 포트를 구현한 클래스 하나와 자기시험과 대조 · 구현체의 선언과 자바독 · 자동설정이 만드는 빈 목록 · 두 스택의 main 참조 수 · 코드 리뷰 문서가 같은 것을 P1 로 적은 자리 — 84줄 · exit 0" zoom="true"
:::
`:37` 주석은 `PolicyTransactionPort.inTransaction(TransactionRequest, Supplier)` 를 지목하고, 같은 문장이 아래의 재시도 코디네이터를 함께 설명한다.
## 그 포트를 구현한 클래스는 하나다
`implements PolicyTransactionPort` 를 가진 파일은 `SpringTransactionPort:31` 하나다. 없는 이름으로 같은 검색식을 걸면 0 이 나오므로, 그 1 이 검색 운은 아닌지 확인하려고 `implements TransactionPort` 를 세면 21 이 나온다.
`SpringTransactionPort``adapter/outbound/persistence-jpa` 에 있고 `@Component` 다. 자바독 `:26`\~`:28` 은 모드별로 미리 만든 `TransactionTemplate` 을 쓰고 모두 `READ_COMMITTED` 로 고정한다고 적는다. 미리 만드는 이유는 가변 템플릿 경합 때문이라고 README 를 가리킨다.
재시도는 그 자바독 어디에도 없다.
## 자동설정이 만드는 것은 다른 스택이다
빈 목록은 여섯이다. `:61``CommitFailureClassifier`, `:66``:82``SpringJpaTransactionExecutor`, `:99``:107``FullTransactionRetryCoordinator`, `:114``RetrySleeper` 다.
포트 구현을 만드는 빈이 없다. `SpringTransactionPort``@Component` 로 스캔되어 들어온다.
그래서 `:37` 주석은 한 문장 안에서 두 계통을 잇는다. 앞이 가리키는 포트는 어댑터 쪽이 구현하고, 뒤가 설명하는 코디네이터는 같은 파일이 만드는 실행기와 짝이다.
## 두 스택 다 살아 있다
`PolicyTransactionPort``FullTransactionRetryCoordinator``SpringJpaTransactionExecutor``SpringTransactionPort` 의 main 참조 파일 수가 모두 4 개다.
`PolicyTransactionPort` 를 참조하는 넷은 그 인터페이스 자신과 `SpringPolicyTransactionPort``SpringTransactionPort``JpaTransactionAutoConfiguration` 이다.
어느 쪽에도 폐기 표시가 없다.
## 저장소 자신이 이미 적어 둔 것
`docs/reviews/2026-08-14-jpa-module-code-review.md:31` 은 애플리케이션이 이미 쓰는 포트와 새 JPA 실행기·AOP 계약이 두 벌로 존재한다고 적는다.
`:119` 는 그것을 `JPA-007` 로 번호 매겨 P1 · High 로 두고 `PolicyTransactionPort` 단일 facade 로 통합하자고 적는다. `:427` 은 템플릿의 정본 경계를 그 포트의 `inTransaction` 으로 삼자고 적는다.
즉 이 상태는 발견되지 않은 것이 아니라 결정이 내려지지 않은 것이다.
## 원문과 갈리는 자리
원문은 문서와 소스 주석이 코디네이터가 포트를 구현한다고 설명하는데 실제 구현체는 다른 클래스라고 적었다. 실제 주석은 그보다 조금 다르다. `:37` 은 코디네이터가 그 포트를 구현한다고 적지 않고, 정본 경계로 포트를 지목한 뒤 아래 코디네이터를 이어서 설명한다. 두 계통이 한 문장에 붙어 있어서 읽는 사람이 그렇게 이해하게 된다.
원문이 적지 않은 것은 저장소가 이미 이 중복을 `JPA-007` 로 기록하고 통합 방향까지 정해 두었다는 것이다.
## 확인하지 못한 것
실제 요청이 두 계통 중 어느 쪽을 더 자주 지나는지 세지 않았다.
그 문장이 쓰였을 때는 맞았는지 커밋을 되짚지 않았다.
`JPA-007` 이 어느 쪽으로 결정됐는지 추적하지 않았다.
`JPA-007` 이 어느 쪽으로 결정됐는지 추적하지 않았다.
<!-- body:end -->