--- 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. 리프 목록을 쓰는 시험 둘이 무엇을 확인하는지 읽는다. ## 본문 이 리프는 하나의 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 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 은 실수가 아니라 역할 분리의 결과이고, 그래서 어느 쪽도 상대를 검사할 이유가 없다. ## 두 시험이 각각 무엇을 보는가 두 시험의 입력이 겹치지 않는다. 리프 시험은 리프의 소스 트리와 자기 카탈로그만, 루트 시험은 루트의 임포트 그래프와 자기 목록만 읽는다. 한쪽 목록이 늘어도 다른 쪽 단언의 입력은 그대로다. 실패할 근거가 없다. ## 고칠 방향 내보내는 패키지 등록부를 한 곳으로 옮기고, 리프의 패키지 그래프 검사와 소비자 규칙이 같은 데이터를 읽게 한다. 분석 문서의 권고가 그것이다. ## 확인하지 못한 것 한쪽 목록에 항목을 더해 다른 쪽이 조용한지 실행으로 확인하지 않았다. 두 선언을 대조하고 참조를 센 것까지가 확인 범위다.