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

9.6 KiB

kind, slug, title, topic, project, status, sourceRevision, rootTreeNode, evidenceCapturedOn, body, assets, evidence, source
kind slug title topic project status sourceRevision rootTreeNode evidenceCapturedOn body assets evidence source
CASE analysis-finding-a05-f010 정본이라던 코디네이터는 그 포트를 구현하지 않고 다른 클래스가 구현한다 multitenancy-isolation clean-architecture-backend-template 게시 전 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 case:analysis-finding-a05-f010 2026-09-04 case-analysis-finding-a05-f010.body.md
key file
analysis-finding-a05-f010 ../../../final/evidence/rendered/analysis-finding-a05-f010.svg
../../../final/evidence/raw/analysis-finding-a05-f010.txt
원본 분석 절은 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. 저장소 문서에서 그 포트 이름이 나오는 자리를 전부 찾는다.

본문

JpaTransactionAutoConfiguration 은 JPA 트랜잭션 스택을 조립한다. 그 파일의 주석이 정본 경계가 어디인지 적어 둔다.

주석이 지목한 경계와 실제 구현 관계

:::evidence key="analysis-finding-a05-f010" alt="저장소 루트에서 돌린 정적 검색 출력 84줄. 먼저 JpaTransactionAutoConfiguration 2855번 줄이 실리는데 37번 줄 주석이 PolicyTransactionPort.inTransaction 을 정본 경계로 지목하고 아래의 재시도 코디네이터를 함께 설명한다. 이어서 PolicyTransactionPort 를 구현한다고 선언한 클래스가 SpringTransactionPort 31번 줄 하나로 나오고, 없는 이름으로 건 자기시험이 0 개, 대조로 센 implements TransactionPort 가 21 개다. 그 구현체의 선언과 자바독이 2440번 줄로 실리는데 모드별로 미리 만든 트랜잭션 템플릿을 쓰고 모두 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 이 나온다.

SpringTransactionPortadapter/outbound/persistence-jpa 에 있고 @Component 다. 자바독 :26~:28 은 모드별로 미리 만든 TransactionTemplate 을 쓰고 모두 READ_COMMITTED 로 고정한다고 적는다. 미리 만드는 이유는 가변 템플릿 경합 때문이라고 README 를 가리킨다.

재시도는 그 자바독 어디에도 없다.

자동설정이 만드는 것은 다른 스택이다

빈 목록은 여섯이다. :61CommitFailureClassifier, :66:82SpringJpaTransactionExecutor, :99:107FullTransactionRetryCoordinator, :114RetrySleeper 다.

포트 구현을 만드는 빈이 없다. SpringTransactionPort@Component 로 스캔되어 들어온다.

그래서 :37 주석은 한 문장 안에서 두 계통을 잇는다. 앞이 가리키는 포트는 어댑터 쪽이 구현하고, 뒤가 설명하는 코디네이터는 같은 파일이 만드는 실행기와 짝이다.

두 스택 다 살아 있다

PolicyTransactionPortFullTransactionRetryCoordinatorSpringJpaTransactionExecutorSpringTransactionPort 의 main 참조 파일 수가 모두 4 개다.

PolicyTransactionPort 를 참조하는 넷은 그 인터페이스 자신과 SpringPolicyTransactionPortSpringTransactionPortJpaTransactionAutoConfiguration 이다.

어느 쪽에도 폐기 표시가 없다.

저장소 자신이 이미 적어 둔 것

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 이 어느 쪽으로 결정됐는지 추적하지 않았다.