refactor: 문서 개선 중

This commit is contained in:
donghyeon-ka
2026-09-21 14:30:55 +09:00
parent c93cdea150
commit 805a18f486
1497 changed files with 525837 additions and 59152 deletions
@@ -83,9 +83,9 @@ preferIPv4Stack 설정 : x
httpclient 어댑터의 mTLS 계약 테스트 다섯 건 중 세 건이 실패한다. 신뢰할 수 없는 CA, 만료된 인증서, 호스트명 불일치다.
## 멈추는 자리는 범주가 아니라 단계
## 실패는 범주 판정보다 두 번째 단계 단언에서 멈춘
:::evidence key="a-red-test-misread-as-a-product-defect" alt="이 리비전에서 모듈 전체 테스트를 실행해 283건 중 3건이 같은 줄에서 실패하는 것을 보인 출력과, 멈추는 자리가 두 번째 단언임을 실제 행 번호로 보인 단언 블록, 픽스처가 호스트명을 돌려주는 메서드, 이 컨테이너의 hosts 항목과 자바가 그 호스트명에서 얻는 주소 둘과 IP 스택 선호 설정 수, 그리고 영구 범주 목록과 재시도 엔진의 가드 순서를 뽑은 출력 55줄. 실패가 범주가 아니라 단계 단언에서 고 호스트명이 주소 둘로 풀린다는 것이 그 출력에 보인다." caption="모듈 전체 283 중 3 실패 · 멈추는 자리는 168행 단계 단언 · 픽스처의 호스트명 · 주소 둘 · 영구 범주 목록과 가드 순서 — 55줄" zoom="true"
:::evidence key="a-red-test-misread-as-a-product-defect" alt="이 리비전에서 모듈 전체 테스트를 실행해 283건 중 3건이 같은 줄에서 실패하는 것을 보인 출력과, 실패 지점이 두 번째 단언임을 실제 행 번호로 보인 단언 블록, 픽스처가 호스트명을 돌려주는 메서드, 이 컨테이너의 hosts 항목과 자바가 그 호스트명에서 얻는 주소 둘과 IP 스택 선호 설정 수, 그리고 영구 범주 목록과 재시도 엔진의 가드 순서를 뽑은 출력 55줄. 출력은 실패가 범주 판정보다 단계 단언에서 먼저 발생하고 호스트명이 주소 둘로 해석된다는 점을 보여 준다." caption="모듈 전체 283 중 3 실패 · 실패 지점은 168행 단계 단언 · 픽스처의 호스트명 · 주소 둘 · 영구 범주 목록과 가드 순서 — 55줄" zoom="true"
:::
단언 블록은 잡은 예외를 분류기에 넣고 넷을 차례로 확인한다. 첫 줄은 통과한다 — 증거가 "보내지 않음"인 것은 맞다.
@@ -106,11 +106,11 @@ httpclient 어댑터의 mTLS 계약 테스트 다섯 건 중 세 건이 실패
분류기는 자기가 받은 것을 정확히 분류했다. 정보는 도착하기 전에 이미 사라졌다.
## 사라진 자리는 이름 해석이
## 이름 해석에서 첫 연결 실패가 다음 주소 시도로 넘어간
픽스처의 URI 메서드가 호스트명 localhost 를 돌려준다. 이 컨테이너의 hosts 파일은 그 이름을 IPv4 와 IPv6 양쪽에 주고, 자바도 두 주소를 돌려준다. 저장소 어디에도 IP 스택 선호를 고정하는 설정이 없다.
목 서버는 IPv4 루프백에만 바인딩한다. Apache 의 연결 오퍼레이터는 해석된 주소를 차례로 시도하는데, 마지막 주소가 아니면 실패를 로그로만 남기고 다음으로 넘어간다. 그 자리 로그 문구가 두 갈래로 나뉜다 — 마지막이면 "작업을 종료한다", 아니면 "다음 주소로 연결을 재시도한다".
목 서버는 IPv4 loopback에만 바인딩한다. Apache의 연결 오퍼레이터는 해석된 주소를 차례로 시도하, 마지막 주소가 아니면 연결 실패를 기록한 뒤 다음 주소로 넘어간다. 로그 문구도 마지막 주소에서는 "작업을 종료한다", 중간 주소에서는 "다음 주소로 연결을 재시도한다"로 갈린다.
첫 주소에서 TCP 는 붙고 핸드셰이크가 깨진다. 그 실패가 버려진다. 두 번째 주소에는 듣는 소켓이 없어 TCP 가 거부되고, 마지막이므로 승격된다.
@@ -127,7 +127,7 @@ httpclient 어댑터의 mTLS 계약 테스트 다섯 건 중 세 건이 실패
클라이언트 인증서를 제시하는 건은 첫 주소에서 핸드셰이크가 성공하고 루프가 즉시 반환한다. 두 번째 주소를 시도하지 않는다.
클라이언트 인증서가 없는 건은 다르다. TLS 1.3 에서 클라이언트가 자기 몫을 끝낸 뒤 서버가 거절하므로 실패가 연결 루프 밖에서 터지고 핸드셰이크 예외 그대로 는다. 그리고 그 테스트는 예외의 종류가 아니라 예외가 났는지만 단언한다.
클라이언트 인증서가 없는 건은 다르다. TLS 1.3에서 클라이언트 쪽 handshake 진행 뒤 서버가 거절하면서 실패가 주소 재시도 루프 밖에서 발생하고, 분류기는 handshake 예외 그대로 는다. 해당 테스트는 예외 타입까지 고정하지 않고 예외 발생 여부만 단언한다.
처음 눈에 띈 차이는 실패하는 세 건만 클라이언트 인증을 요구하지 않는 픽스처를 쓴다는 점이었다. 그것은 원인이 아니라 상관이었다.
@@ -0,0 +1,58 @@
---
id:
kind: CONCEPT
slug: three-failure-vocabularies
title: 실패 어휘 세 층과 그 사이를 잇는 SQLState 매트릭스
topic: http-failure-classification
topicName: HTTP 실패 분류와 재시도 안전성
project: clean-architecture-backend-template
status: 게시 전
studio: ""
basisVersion: sourceRevision 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
source:
- final/document.md#a02
- final/document.md#a05
- final/document.md#5-1
- final/document.md#a05 §7.1
- final/document.md#a02
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
---
# 실패 어휘 세 층과 그 사이를 잇는 SQLState 매트릭스
저수준 driver 오류, 애플리케이션 failure category, 외부 HTTP 응답은 서로 다른 질문에 답한다. 이 프로젝트는 그 사이를 명시적 매핑으로 연결하고, 모르는 값은 추측하지 않는다.
## 관계
- **인식하지 못한 SQLSTATE 는 추측하지 않는다**
매트릭스에 없는 값을 임의의 failure category로 바꾸지 않는 결정이다.
- **같은 SQLState 를 둘이 등록하면 값이 같아도 시작을 실패시킨다**
매핑 소유권을 한 곳으로 유지하는 규칙이다.
- **전송 여부 판정은 evidence와 operation semantics를 함께 본다**
failure category가 곧 retry eligibility가 아니라는 후속 결정이다.
## 본문
<!-- body:start -->
## 첫 번째 어휘는 공급자 오류다
JDBC나 HTTP client는 SQLSTATE, status, exception type처럼 공급자에 가까운 값을 준다. 이 값은 어떤 기술 경계에서 실패했는지를 말하지만 애플리케이션 정책을 직접 결정하지 않는다.
## 두 번째 어휘는 failure category다
애플리케이션은 transient, conflict, unavailable처럼 정책이 이해할 수 있는 범주로 변환한다. PostgreSQL 경로에서는 SQLSTATE 매트릭스가 이 변환을 소유한다.
매트릭스에 없는 SQLSTATE는 기존 항목과 비슷해 보인다는 이유로 추측하지 않는다. 새 매핑이 필요하면 그 소유자를 명시적으로 추가한다.
## 세 번째 어휘는 외부 계약이다
HTTP 응답은 클라이언트가 알아야 할 상태와 오류 코드를 표현한다. 내부 failure category와 1:1일 필요는 없고, 내부 구현 세부가 그대로 노출되어서도 안 된다.
## retry는 별도 판정이다
failure category는 retry 입력 중 하나다. 최종 retry eligibility는 전송 evidence, operation의 멱등성·의미, deadline과 retry budget을 함께 본다.
현재 source repository를 다시 실행하지 못했으므로 이 기록은 SSOT가 고정한 매핑 구조를 설명한다.
<!-- body:end -->
@@ -0,0 +1,45 @@
---
kind: PROJECT_DECISION
slug: an-unrecognised-sqlstate-is-not-guessed
title: 인식하지 못한 SQLSTATE 는 추측하지 않는다
topic: http-failure-classification
topicName: HTTP 실패 분류와 재시도 안전성
project: clean-architecture-backend-template
status: 게시 전
decisionStatus: ADOPTED
source:
- final/document.md#a05
- final/document.md#10-2
- final/document.md#5-1
- final/document.md#a05 §7.1
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
---
# 인식하지 못한 SQLSTATE 는 추측하지 않는다
매트릭스에 등록되지 않은 SQLSTATE를 이름이나 class prefix가 비슷하다는 이유로 기존 failure category에 넣지 않는다.
## 근거
- **실패 어휘 세 층과 그 사이를 잇는 SQLState 매트릭스**
공급자 오류와 애플리케이션 범주 사이에 명시적 번역 계층을 둔다.
- **같은 SQLState 를 둘이 등록하면 값이 같아도 시작을 실패시킨다**
매핑의 값과 소유권을 둘 다 명시적으로 유지한다.
## 결정문
등록되지 않은 SQLSTATE는 기존 category로 추측해 분류하지 않는다. 정책에 필요한 상태라면 매트릭스에 명시적으로 등록하고 해당 매핑의 소유자를 정한다.
## 판단 이유
SQLSTATE의 접두사나 주변 예외가 비슷해도 operation semantics와 retry 안전성이 같다는 보장은 없다. 추측 분류는 새로운 데이터베이스 상태를 기존 정책에 조용히 편입시킨다.
명시 등록을 요구하면 새 상태를 지원하는 순간 코드 리뷰와 테스트에 변경점이 생긴다. 알 수 없는 값을 아는 값처럼 다루지 않는 쪽을 선택한다.
## 영향
감수하는 것 : 새로운 SQLSTATE가 나타나면 매핑을 추가하기 전까지 자동 복구 정책을 적용하지 못할 수 있다.
얻는 것 : 미지의 오류가 retryable 또는 permanent로 조용히 오분류되지 않는다.
얻는 것 : 매핑 추가 시 어떤 모듈이 그 의미를 소유하는지 함께 검토할 수 있다.
@@ -21,15 +21,15 @@ source:
# 재시도 안전성은 증거에 기반해 판정한다
재시도 여부는 무엇이 실패했는지가 아니라 무엇이 관측됐는지로 판정한다. 요청이 서버에 닿지 않았으면 재시도는 첫 시도이고 닿았을지도 모르면 중복인데, 예외 타입은 그 구분을 담지 않는다.
전송 여부와 commit ambiguity는 관측 evidence로 판정한다. 다만 최종 재시도 eligibility는 그 evidence만으로 정하지 않고 failure category, operation의 멱등성·의미, deadline·retry budget·policy를 함께 본다.
## 결정문
재시도 여부는 무엇이 실패했는지가 아니라 무엇이 관측됐는지로 판정한다.
전송 여부 판정은 evidence에 기반하고, 최종 재시도 여부는 evidence와 failure category, operation semantics, retry budget을 함께 보고 정한다.
## 판단 이유
같은 예외라도 요청이 서버에 닿았는지에 따라 재시도의 의미가 완전히 달라진다. 닿지 않았으면 재시도는 첫 시도이고, 닿았을지도 모르면 재시도는 중복이다. 예외 타입 그 구분을 담지 않으므로, 관측된 진행 정도를 별도의 값으로 기록하고 그것을 판정 입력으로 삼는다.
같은 예외라도 요청이 서버에 닿았는지에 따라 재시도의 의미가 달라진다. 닿지 않았다는 evidence가 있으면 transmission ambiguity가 줄고, 닿았을 가능성이 있으면 중복 실행 위험을 고려해야 한다. 예외 타입만으로는 그 구분이 부족하므로 관측된 진행 정도를 별도의 값으로 기록한다. 그 값은 최종 결정의 한 축이며 failure category와 멱등성·operation semantics, 남은 deadline과 retry budget도 함께 입력으로 들어간다.
이 판정을 보수적으로 유지하는 것이 핵심이다. 전송되지 않았다는 판정은 단계 실패가 그것을 증명할 때만 쓰고, 일반적인 엔진 입출력 실패는 결코 그 판정으로 승격되지 않는다. 모호한 것을 전송되지 않음으로 추측하는 것이 타임아웃을 중복 결제로 바꾸는 경로다.
@@ -49,7 +49,7 @@ source:
같은 실패에 대해 Apache JDK Reactor Netty Jetty 가 동일한 재시도와 관측 동작을 낸다.
재시도 정책이 HTTP 메서드 같은 간접 신호에 기대지 않다.
재시도 정책이 HTTP 메서드 하나 같은 간접 신호에 기대지 않고 전송 evidence와 operation semantics를 함께 사용한다.
## 근거
@@ -0,0 +1,52 @@
---
kind: REFERENCE
slug: one-sqlstate-with-two-contributors-fails-startup
title: 같은 SQLState 를 둘이 등록하면 값이 같아도 시작을 실패시킨다
topic: http-failure-classification
topicName: HTTP 실패 분류와 재시도 안전성
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
source:
- final/document.md#a05
- final/document.md#5-1
- final/document.md#a05 §7.1
---
# 같은 SQLState 를 둘이 등록하면 값이 같아도 시작을 실패시킨다
여러 모듈이 같은 SQLSTATE 매트릭스에 기여할 때는 최종 값이 같더라도 중복 등록을 허용하지 않는다. 매핑 값뿐 아니라 어느 모듈이 그 항목을 소유하는지도 계약의 일부로 본다.
## 관계
- **실패 어휘 세 층과 그 사이를 잇는 SQLState 매트릭스**
이 규칙이 적용되는 매핑 계층을 설명한다.
- **인식하지 못한 SQLSTATE 는 추측하지 않는다**
매핑 누락과 중복을 모두 명시적으로 다루는 짝이 되는 결정이다.
## 목적
last-writer-wins 병합이 매핑 소유권 충돌을 숨기지 못하게 한다.
## 규칙
1. 한 SQLSTATE는 한 contributor가 소유한다.
2. 두 contributor가 같은 SQLSTATE를 등록하면 매핑 결과가 같아도 startup validation을 실패시킨다.
3. 충돌을 해결하려면 한쪽 등록을 제거하거나 소유권을 하나로 합친다.
4. map merge 결과만 비교하지 않고 contributor identity를 함께 검사한다.
## 적용 조건
여러 모듈이 같은 SQLSTATE 또는 오류 코드 매트릭스에 항목을 기여할 때
여러 기여자의 등록 결과가 런타임 failure classification을 결정할 때
## 예외
기여자가 하나뿐인 매트릭스에는 중복 소유권 검사가 필요하지 않다.
중복 등록 자체를 계약으로 허용하려면 어떤 contributor가 우선하는지 별도 규칙과 검증이 있어야 한다. 현재 이 프로젝트는 그 방식을 선택하지 않는다.
## 예시
모듈 A와 B가 모두 같은 SQLSTATE를 같은 category로 등록해도 “결과가 같으니 괜찮다”고 병합하지 않는다. 둘 중 누가 이 매핑을 변경해야 하는지 알 수 없기 때문이다.