Files
document-haness/docs/clean-architecture-backend-template/tech-log-studio/identity-and-identifier/case/case-a07-f001-uuidcodec.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

177 lines
13 KiB
Markdown

---
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:
- 원본 분석 절은 analysis/07-adapter-outbound-identifier.md#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. 레지스트리에서 이 리프의 소비자 후보를 센다.
## 본문
<!-- body:start -->
모듈 문서가 이 리프를 아웃바운드 어댑터 모듈 밖에 둔 이유를 세 줄로 적는다.
## 논거와 공개 타입
:::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
* <p>{@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 이라 의존 문제 자체가 없음).
```
모듈을 그 자리에 둔 규칙이 그 모듈의 능력을 부를 수 없게 만든다.
## 남는 선택지
이 타입은 서른 줄짜리 유틸이고 자기 명세를 통과한다. 논거에서 그 능력을 빼거나, 배치 규칙을 다시 여는 것이 남는다. 단일 경로로 올리는 쪽은 규칙 변경이 선행이고, 그 다음에는 같은 문서 다음 절이 다루는 정규화 문제가 온다.
이 기록이 센 것은 소비자의 유무다. 단일 경로 여부를 물으면 앞의 두 타입도 통과하지 못한다 — 리프 밖 프로덕션 스물다섯 파일이 표준 라이브러리 생성기를 직접 부른다.
## 확인하지 못한 것
스물한 줄 중 열세 줄은 모듈 의존 규칙상 이 타입을 부를 수 없어 동작 비교를 물을 단계가 아니다. 규칙이 허용하는 여덟 줄에 대해서만 대체 시 동작이 같은지 확인하지 않았다.
<!-- body:end -->