--- 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: - 원본 분석 절은 final/document.md#a05 §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 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` 이 어느 쪽으로 결정됐는지 추적하지 않았다.