--- kind: CASE slug: a07-f001-uuidcodec title: UuidCodec 의 메서드를 부르는 줄은 자기 명세 다섯뿐이다 topic: identity-and-identifier project: clean-architecture-backend-template status: 게시 전 sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 rootTreeNode: case:a07-f001-uuidcodec evidenceCapturedOn: 2026-09-02 body: case-a07-f001-uuidcodec.body.md assets: - key: a07-f001-uuidcodec file: ../../../final/evidence/rendered/a07-f001-uuidcodec.svg - key: a07-f001-uuidcodec-elsewhere file: ../../../final/evidence/rendered/a07-f001-uuidcodec-elsewhere.svg evidence: - ../../../final/evidence/raw/a07-f001-uuidcodec.txt - ../../../final/evidence/raw/a07-f001-uuidcodec-elsewhere.txt source: - 원본 분석 절은 final/document.md#a07#L73 이다. 등급은 P2 다. 세 타입의 리프 밖 소비자 계수, 메서드 호출이 자기 명세뿐이라는 사실, 문자열에서 직접 만드는 경로가 여럿이라는 지적, 컬럼 변환을 하이버네이트가 처리한다는 서술, 그리고 수정 두 가지가 그 절에 있다. - 그 절은 직접 만드는 파일이 스무 개 이상이라 적는데, 그 절이 예시로 든 다섯이 모두 프로덕션 소스이므로 같은 정의에서 맞는 수는 열다섯 파일 스물한 줄이다. 소스 세트를 가리지 않고 세면 서른넷이고 그중 열아홉이 테스트다. - 애너테이션이 잇는 구간이 UUID 값과 컬럼 사이라는 것, GraphQL 스칼라가 표준 파서보다 좁게 거른다는 것, 그리고 모듈 의존 규칙이 스물한 줄 중 열세 줄을 막는다는 것은 이 기록에서 확인했다. --- # UuidCodec 의 메서드를 부르는 줄은 자기 명세 다섯뿐이다 이 리프를 아웃바운드 어댑터 모듈 밖에 둔 근거로 UUID 식별자와 코덱 능력이 제시된다. 그 능력을 구현한 타입을 부르는 프로덕션 코드가 없다. ## 관계 - **@Bean이 있다는 것은 조립 증거가 아니다** 타입의 존재와 사용을 나눠 세는 규칙이다. - **소비자가 없는 fixture 셋** 선언된 타입 수와 프로덕션 호출자 수를 각각 세어 배선되지 않았다는 것을 확인한 다른 리프다. - **normalize는 canonical이 아닌 입력을 받아 다른 UUID로 조용히 바꾼다** 이 타입을 단일 경로로 올릴 때 먼저 고쳐야 하는 것이다. - **문서가 선언됐다는 둘은 없고, 선언된 하나는 그 문장에 없다** 같은 리프의 문서 정합성 사례다. ## 문제 모듈 문서가 배치 근거를 한 문장으로 적는다. UUID 식별자와 코덱 능력은 인프라이지 아웃바운드 연동 지점이 아니므로 그 모듈 밖에 둔다는 것이다. 리프의 공개 타입은 셋이다. 가명화기와 업로드 식별자 팩토리와 UUID 코덱이다. ## 결론 앞의 둘은 리프 밖에서 생성된다. 가명화기는 부트스트랩과 예제 애플리케이션에서, 업로드 식별자 팩토리는 부트스트랩에서다. 셋째는 없다. 그 이름이 리프 밖에 나오는 자리는 넷인데 둘은 몽고 테스트킷이 임포트하는 드라이버의 동명 타입이고 하나는 그 드라이버 설정을 적은 ADR 문서다. 이 리프의 타입을 가리키는 것은 하나뿐이고, 그것은 호출이 아니라 예제 애플리케이션 README 의 산문이다. 그 산문은 코덱을 쓰지 않는 이유를 적는다. 메서드를 부르는 줄은 저장소 전체에서 다섯이고 다섯 다 자기 명세다. 리프 밖 프로덕션에서 문자열을 UUID 로 바꾸는 자리를 세면 파일 열다섯에 줄 스물하나다. 그 열다섯이 하는 일이 다 같지는 않다. GraphQL 의 UUID 스칼라는 표준 파서가 관대하다는 것을 자바독에 적고 정규형 정규식으로 먼저 거른 뒤에야 파서를 부른다. 코덱으로 갈아 끼우면 검사 폭이 줄어든다. 문서가 코덱의 다른 목적으로 든 컬럼 변환은 하이버네이트의 타입 코드 애너테이션이 맡는다. 다만 그 애너테이션이 붙은 서른여덟 자리의 필드 타입은 전부 UUID 다. 애너테이션이 잇는 것은 UUID 값과 컬럼 사이이고, 문자열과 UUID 사이는 여전히 손으로 짜여 있다. 예제의 영속 매퍼가 그 두 겹을 한 파일에서 보여 준다. 이 분산은 배치 규칙의 결과다. 모듈 레지스트리에서 이 리프에 의존해도 되는 모듈은 예순둘 중 둘, 부트스트랩과 예제 애플리케이션뿐이다. 그중 열세 줄은 규칙을 손대기 전에는 그 타입에 닿을 수 없는 자리다. 남는 여덟 줄 중 둘은 예제 README 가 쓰지 않는 이유를 이미 적어 두었다. 코드 자체에는 결함이 없다. 서른 줄짜리 유틸이고 자기 시험은 다 통과한다. 어긋난 것은 논거다. 그래서 선택지는 좁다. 능력을 논거에서 지우거나 배치 규칙을 손보는 것뿐이다. 그 타입을 실제 단일 경로로 올리려면 규칙을 먼저 바꿔야 하고, 그 다음에는 같은 문서 다음 절이 다루는 정규화 문제를 고쳐야 한다. 이 기록이 세는 것은 소비자의 유무이지 단일 경로 여부가 아니다. 단일 경로를 물으면 앞의 둘도 통과하지 못한다. 리프 밖 프로덕션 스물다섯 파일이 표준 라이브러리 생성기를 직접 부른다. ## 검증 환경 확인 방식 : 저장소 전수 참조 계수, 모듈 의존 레지스트리 조회 소스 수정 : x ## 재현 조건 1. 모듈 문서가 적은 배치 근거를 읽는다. 2. 리프의 공개 타입을 나열한다. 3. 각 타입 이름이 리프 밖에 나오는 곳을 확장자 제한 없이 전수로 센다. 4. 그중 이 리프의 타입을 가리키는 것과 동명의 다른 타입을 가른다. 5. 그 타입의 메서드를 부르는 줄을 저장소 전체에서 센다. 6. 문자열에서 UUID 를 직접 만드는 리프 밖 프로덕션 파일과 줄을 전부 나열한다. 7. 그중 표준 파서를 그대로 쓰지 않는 것이 있는지 확인한다. 8. 컬럼 변환 애너테이션이 붙은 필드의 타입을 센다. 9. 레지스트리에서 이 리프의 소비자 후보를 센다. ## 본문 모듈 문서가 이 리프를 아웃바운드 어댑터 모듈 밖에 둔 이유를 세 줄로 적는다. ## 논거와 공개 타입 :::evidence key="a07-f001-uuidcodec" alt="모듈 문서가 적은 배치 근거 세 줄과 리프의 공개 타입 셋. 각 타입 이름이 리프 밖에 나오는 곳을 확장자 제한 없이 전수로 검색한 결과. 그리고 UUID 코덱의 메서드를 부르는 줄을 저장소 전체에서 검색한 결과를 출력한 터미널 기록." caption="가명화기는 리프 밖 생성 둘, 업로드 식별자 팩토리는 하나 · 코덱 이름이 나오는 넷 중 둘은 드라이버의 동명 타입, 하나는 ADR 문서, 하나는 예제 README 의 산문 · 메서드 호출은 자기 명세 다섯 줄 — 40줄 · exit 0" zoom="true" ::: ```text - Kept out of `adapter-outbound` on purpose: a UUID id/codec capability is infrastructure, not an outbound integration point, so `adapter-outbound` keeps its documented meaning (external HTTP / messaging / cache / notifications). ``` 능력은 하나로 적혀 있고, 공개 타입은 셋이다. 셋 중 둘은 리프 밖에서 생성된다 — 가명화기는 부트스트랩과 예제 애플리케이션에서, 업로드 식별자 팩토리는 부트스트랩에서. ## 이름이 겹친 참조들 코덱 이름이 리프 밖에 나오는 자리는 넷이다. ```text docs/adr/ADR-MONGO-002-bson-representation.md:44:pins `UuidCodec(STANDARD)` explicitly … src/sample-portfolio/README.md:333:- … `UuidCodec` 같은 공용 src/adapter/outbound/persistence-mongo/src/testkit/.../MongoBsonSnapshot.java:16:import org.bson.codecs.UuidCodec; src/adapter/outbound/persistence-mongo/src/testkit/.../MongoBsonSnapshot.java:59: … new UuidCodec(UuidRepresentation.STANDARD)), ``` 뒤 둘은 몽고 드라이버의 동명 타입이고, ADR 은 그 드라이버 설정을 적은 문서다. 이 리프의 타입을 가리키는 것은 예제 README 한 줄뿐인데, 그 줄은 호출이 아니라 쓰지 않는 이유를 적는 산문이다. 저장소 전체에서 그 타입의 메서드를 부르는 줄은 다섯이고, 다섯 다 자기 명세 파일이다. ## 같은 변환을 하는 다른 자리들 :::evidence key="a07-f001-uuidcodec-elsewhere" alt="문자열에서 UUID 를 직접 만드는 리프 밖 프로덕션 파일 수와 호출 줄 수와 그 파일 전부의 목록. 그중 GraphQL 스칼라가 표준 파서의 관대함을 적고 정규형 정규식으로 먼저 거르는 구간. 컬럼 변환 애너테이션을 단 프로덕션 파일 수와 애너테이션 수와 그 애너테이션이 붙은 필드의 타입 분포. 예제의 영속 매퍼가 문자열 구간을 따로 처리하는 줄. 그리고 모듈 레지스트리에서 이 리프에 의존해도 되는 모듈 수와 예제 README 가 코덱을 쓰지 않는 이유를 적은 세 줄을 출력한 터미널 기록." caption="직접 만드는 곳은 열다섯 파일 스물한 줄 · GraphQL 스칼라는 정규형 정규식으로 먼저 거름 · 컬럼 변환 애너테이션은 스물세 파일 서른여덟 개이고 붙은 필드는 전부 UUID · 리프에 의존해도 되는 모듈은 예순둘 중 둘 — 43줄 · exit 0" zoom="true" ::: 문자열에서 UUID 를 만드는 일은 리프 밖 프로덕션 열다섯 파일에서 스물한 줄이 한다. 애플리케이션 코어의 파일·업로드 식별자, 세 메시징 리프의 매퍼, GraphQL 의 UUID 스칼라, 몽고의 커서 코덱, 알림의 라우팅 계획 코덱, JPA 의 멱등 청구 저장소, 예제의 웹 컨트롤러 넷과 영속 매퍼 둘이다. 열다섯이 모두 같은 일을 하지는 않는다. ```java *

{@link UUID#fromString} is lenient — it happily accepts {@code "1-1-1-1-1"} — so accepting * whatever it parses would make the wire contract depend on a JDK quirk and let two different * strings denote the same identifier. The canonical form is enforced explicitly instead. ``` GraphQL 스칼라는 표준 파서의 관대함을 알고 정규형 정규식으로 먼저 거른다. 이쪽을 코덱으로 바꾸면 검사가 느슨해진다. ## 컬럼 변환은 다른 구간을 잇는다 ```text # @JdbcTypeCode(SqlTypes.UUID) 를 단 프로덕션 파일 : 23 # 그 애너테이션 개수 : 38 # 그 애너테이션이 붙은 필드의 타입 : {'UUID': 38} ``` 애너테이션이 잇는 것은 UUID 값과 PostgreSQL 컬럼 사이다. 문서가 `toUuid`/`fromUuid` 의 목적으로 적은 두 구간 중 문자열과 UUID 사이는 여기에 없고, 위의 스물한 줄이 각자 처리한다. 예제의 영속 매퍼가 두 겹을 한 파일에서 보여 준다 — 엔티티는 애너테이션으로 컬럼을 잇고, 매퍼가 표준 파서로 문자열을 잇는다. ```java public static UUID toUuid(WorkLogId id) { return UUID.fromString(id.value()); ``` ## 이 분산은 배치 규칙의 결과다 ```text # 전체 62 중 2 : app-bootstrap, sample-portfolio ``` 모듈 레지스트리에서 이 리프에 의존해도 되는 모듈은 둘뿐이다. 스물한 줄 중 열세 줄은 규칙을 먼저 바꾸지 않으면 그 타입을 부를 수 없다. 남는 여덟 줄 중 둘에 대해서는 예제 README 가 이유를 적어 두었다. ```text - 36자 canonical UUID 와 PostgreSQL native `uuid`(128비트)를 서로 변환합니다. `UuidCodec` 같은 공용 코덱이 아니라 JDK `java.util.UUID` 를 **직접** 쓰는 이유: 영속 어댑터는 경계 규칙상 `adapter-outbound`(코덱이 있는 곳)에 의존하면 안 되기 때문입니다(stdlib 이라 의존 문제 자체가 없음). ``` 모듈을 그 자리에 둔 규칙이 그 모듈의 능력을 부를 수 없게 만든다. ## 남는 선택지 이 타입은 서른 줄짜리 유틸이고 자기 명세를 통과한다. 논거에서 그 능력을 빼거나, 배치 규칙을 다시 여는 것이 남는다. 단일 경로로 올리는 쪽은 규칙 변경이 선행이고, 그 다음에는 같은 문서 다음 절이 다루는 정규화 문제가 온다. 이 기록이 센 것은 소비자의 유무다. 단일 경로 여부를 물으면 앞의 두 타입도 통과하지 못한다 — 리프 밖 프로덕션 스물다섯 파일이 표준 라이브러리 생성기를 직접 부른다. ## 확인하지 못한 것 스물한 줄 중 열세 줄은 모듈 의존 규칙상 이 타입을 부를 수 없어 동작 비교를 물을 단계가 아니다. 규칙이 허용하는 여덟 줄에 대해서만 대체 시 동작이 같은지 확인하지 않았다.