docs(clean-architecture-backend-template): 제1부가 채택한 것만 글감으로 남기고 다시 고른다
글감 1,001개 중 제1부(§3~§11) 앵커를 하나라도 가진 것은 112개뿐이었다. 나머지 889개는
제2부 모듈 분석 65편의 절 제목에서 나온 것이고, 그것이 재판정이 필요했던 이유다.
주제 44 → 16 (43개가 독자 질문 없이 있었다. 지금은 전부 있다)
글감 1,001 → 123 (제1부 앵커 112 + 제1부가 채택했는데 비어 있던 자리 11)
후보 965 → 1,088 · PENDING 905 → 0
error 3,042 → 0
내려온 889개는 후보 대장에 KEEP_IN_SSOT 로 남는다 — 버린 것이 아니라 분석에 남기고 독립
기록으로 만들지 않기로 한 것이다. 그 글감을 받치던 기록 파일 828개는 지웠다. 계약이 정본이고,
파일이 남아 있다는 이유로 계약에서 뺀 주제가 되살아나면 안 된다. 이력에는 그대로 있다 —
git checkout a0ca2bb -- <경로>.
제1부가 채택했는데 글감이 없던 자리 열하나를 채웠다: mongo high-water mark 가 재전달 이벤트를
삼킨 P1, admin plane 이 가드만 켜고 서비스는 켜지 않은 것과 그 짝인 결정, 실패 어휘 세 층과
SQLState 매트릭스 병합 규칙, 부하 아래에서만 새는 admission 경계, 발행 증거와 완료 판정의
분리, keyset·JSONB 결정 둘.
Concept 17개에 basis-version 을 채우고, 계약 제목과 기록 제목이 갈라져 있던 23건을 기록 쪽에
맞췄다. candidateScope 에 excludedAnchorPattern 을 적어 제2부 앵커만 가진 글감이 다시 올라올
수 없게 한다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a0ca2bb72a
commit
1f04117bbf
-118
@@ -1,118 +0,0 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: a-circuit-breaker-permit-that-leaks-on-local-rejection
|
||||
title: 로컬 거부 경로에서 회로 브레이커 permission이 반환되지 않는다
|
||||
topic: http-failure-classification
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:a-circuit-breaker-permit-that-leaks-on-local-rejection
|
||||
evidenceCapturedOn: 2026-09-02
|
||||
body: case-a-circuit-breaker-permit-that-leaks-on-local-rejection.body.md
|
||||
assets:
|
||||
- key: a-circuit-breaker-permit-that-leaks-on-local-rejection
|
||||
file: ../../../final/evidence/rendered/a-circuit-breaker-permit-that-leaks-on-local-rejection.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/a-circuit-breaker-permit-that-leaks-on-local-rejection.txt
|
||||
source:
|
||||
- 원본 분석 절은 final/document.md#a11 §22 이다. permission 획득·반환 경로의 전수 추적은 그 분석이 인용하는 원시 증거 170 번 §8.1 에 있다.
|
||||
---
|
||||
|
||||
# 로컬 거부 경로에서 회로 브레이커 permission이 반환되지 않는다
|
||||
|
||||
회로 브레이커 permission을 얻은 뒤 rate limiter나 bulkhead가 요청을 거부하면 그 permission이 반환되지 않는다. HALF_OPEN 상태에서는 시험 슬롯이 영구히 소비되어, 회복한 업스트림에 대해 회로가 닫히지 않을 수 있다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **전송 실패의 단계와 범주 — AttemptStage와 FailureCategory**
|
||||
이 파이프라인이 그 분류를 만들기 전에 지나는 승인 계층이다.
|
||||
|
||||
## 문제
|
||||
|
||||
요청 하나가 실행되기 전에 세 가드를 차례로 지난다. 회로 브레이커가 permission 을 주고, rate limiter 가 토큰을 주고, bulkhead 가 슬롯을 준다.
|
||||
|
||||
뒤의 두 가드가 거부하면 그 경로는 예외를 던지고 끝난다. 그 사이에 이미 받아 둔 회로 permission 을 돌려주는 호출이 없다.
|
||||
|
||||
## 결론
|
||||
|
||||
경로 확인으로 확정한 결함이다. 회로가 반쯤 열린 상태에서만 발생한다.
|
||||
|
||||
업스트림 장애로 회로가 열린다. 대기 후 반쯤 열린 상태로 바뀌고, 트래픽이 돌아온다. 그 순간 평상시 부하에 맞춰 사이징된 로컬 rate limiter 나 bulkhead 가 거부하기 시작하고, 거부마다 시험 슬롯 하나가 사라진다.
|
||||
|
||||
거부가 허용된 시험 호출 수만큼 쌓이면 브레이커는 성공도 실패도 못 본 채 그 상태에 머문다. 회복한 업스트림에 대해 회로가 닫히지 않는다.
|
||||
|
||||
수정하려면 인터페이스에 permission 반환 연산을 추가하고, rate limiter 와 bulkhead 가 요청을 거부하는 두 경로에서 그 연산을 호출해야 한다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12
|
||||
Gradle : 9.0.0
|
||||
Spring Boot : 4.0.8
|
||||
Resilience4j : 2.2.0
|
||||
확인 방식 : 파이프라인 진입부의 예외 경로 추적, 인터페이스 연산 전수 확인, 반환 연산 이름 검색
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
1. AttemptResiliencePipeline.execute 의 83행부터 97행까지를 읽고, 세 가드의 거부 경로에서 회로 브레이커 연산이 호출되는지 확인한다.
|
||||
2. AttemptCircuitBreaker 의 추상 연산을 전수 확인한다. 넷이며 반환 연산이 없다.
|
||||
3. releasePermission 을 코드베이스에서 검색한다. Java 매치가 0 이다.
|
||||
4. AttemptResiliencePipelineTest 에서 회로 permission 반환을 단언하는 테스트가 있는지 확인한다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
`AttemptResiliencePipeline.execute` 진입부에서 세 가드가 차례로 실행된다. 83행이 회로 permission 을 얻고, 88행이 rate limiter 를, 93행이 bulkhead 를 본다.
|
||||
|
||||
83행을 통과한 뒤 88행과 93행이 거부하면 그 두 경로는 예외를 던지고 끝난다. 회로 permission 이 돌아오지 않는다.
|
||||
|
||||
## 반환할 연산 자체가 인터페이스에 없다
|
||||
|
||||
:::evidence key="a-circuit-breaker-permit-that-leaks-on-local-rejection" alt="코드베이스에서 AttemptResiliencePipeline 의 가드와 반납 지점, AttemptCircuitBreaker 의 추상 연산 전수, releasePermission 검색 결과를 뽑은 출력 26줄. 인터페이스의 연산 넷과 Java 코드 매치 0, 그리고 그 이름이 설계 문서에만 남아 있다는 것이 그 출력에 그대로 보인다." caption="세 가드 · 정상 경로 반납 · AttemptCircuitBreaker 연산 넷 · releasePermission 검색 — 26줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
`AttemptCircuitBreaker` 의 추상 연산은 넷이다 — `tryAcquirePermission` · `onSuccess` · `onError` · `state`. 획득은 있고 반환은 없다.
|
||||
|
||||
`releasePermission` 은 `src` 아래 Java 파일에서 매치가 0 이다. 저장소 전체로 넓히면 한 곳에 나오는데, 그것은 이 능력의 설계 문서다. 즉 이름이 설계 단계에서는 존재했고 구현에는 들어오지 않았다.
|
||||
|
||||
로컬 거부 경로가 부르는 것을 잊은 것이 아니라, 부를 수 있는 연산이 없다.
|
||||
|
||||
## 같은 거부 경로에서 rate 토큰은 돌려준다
|
||||
|
||||
94행이 이 판정을 뒷받침한다. bulkhead 가 거부하는 경로는 `rateLimiter.onCompleted()` 를 불러 rate 토큰을 명시적으로 반환한다. 저자가 permit 반환을 의식하고 있었다는 증거다.
|
||||
|
||||
정상 경로에도 같은 의식이 보인다. `releaseAttemptPermits()` 가 `bulkhead.release()` 와 `rateLimiter.onCompleted()` 를 함께 부른다 — 자료의 120행이 그 두 번째 호출이다. 세 가드 중 둘은 정상 경로에서도 거부 경로에서도 반납되고, 회로만 어느 쪽에서도 반납되지 않는다.
|
||||
|
||||
## 이 결함은 HALF_OPEN 에서만 값을 갖는다
|
||||
|
||||
Resilience4j 의 `tryAcquirePermission()` 은 반쯤 열린 상태에서 허용된 시험 호출 수 중 하나를 소비한다. 그 슬롯은 `onSuccess` · `onError` · `releasePermission` 중 하나로만 돌아온다. 아무것도 부르지 않으면 슬롯은 영구히 소비된다.
|
||||
|
||||
닫힌 상태에서는 permission 이 계수를 소비하지 않으므로 같은 코드가 무해하다. 그래서 이 결함은 코드가 아니라 상태에 걸려 있고, 평상시 테스트로는 드러나지 않는다.
|
||||
|
||||
## 조건들이 우연히 겹치지 않는다
|
||||
|
||||
시험 슬롯이 열리는 시점은 업스트림이 회복을 시작한 시점이고, 트래픽이 돌아오는 시점도 같다. 평상시 부하에 맞춰 사이징된 로컬 가드는 그 순간에 거부하기 시작한다.
|
||||
|
||||
거부마다 슬롯 하나가 사라진다. 허용된 시험 호출 수만큼 거부가 나면 브레이커는 성공도 실패도 관측하지 못한 채 그 상태에 머문다. `maxWaitDurationInHalfOpenState` 기본값이 0 — 무한 대기 — 이므로 시간이 그것을 풀어 주지도 않는다.
|
||||
|
||||
## 테스트가 그 공백을 그대로 보여 준다
|
||||
|
||||
인접한 두 성질에는 테스트가 있다.
|
||||
|
||||
- `openCircuitDoesNotConsumeRateOrBulkheadPermit` — 회로가 거부할 때 뒤의 둘을 소비하지 않는다
|
||||
- `bulkheadRejectionReleasesTheRateLimiterAndIsNotACircuitError` — bulkhead 거부가 rate 를 돌려준다
|
||||
|
||||
둘째 테스트가 단언하는 이벤트 순서는 `circuit-enter` · `rate-enter` · `bulkhead-reject` · `rate-exit` 다. 회로를 돌려주는 이벤트가 그 목록에 없다. 88행의 rate limiter 거부 경로에는 테스트가 아예 없다.
|
||||
|
||||
## 수정
|
||||
|
||||
`AttemptCircuitBreaker` 에 `releasePermission()` 을 더해 Resilience4j 의 같은 이름 연산에 위임하고, `alwaysClosed()` 구현에서는 아무것도 하지 않게 둔다. 그리고 두 로컬 거부 경로에서 그것을 부른다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
시험 슬롯 고갈을 반복 호출로 재현해 보지는 않았다. 회로가 닫히지 않는 상태를 런타임에서 관측한 것은 아니다.
|
||||
|
||||
Resilience4j 의 상태별 permission 회계와 무한 대기 기본값은 그 라이브러리의 문서화된 동작을 근거로 삼았고, 이 회차에 라이브러리 코드를 실행해 확인하지는 않았다.
|
||||
|
||||
<!-- body:end -->
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
---
|
||||
kind: REFERENCE
|
||||
slug: a-classifier-sees-only-what-the-engine-kept
|
||||
title: 분류기는 엔진이 남긴 것만 볼 수 있다
|
||||
topic: http-failure-classification
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: reference:a-classifier-sees-only-what-the-engine-kept
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
---
|
||||
|
||||
# 분류기는 엔진이 남긴 것만 볼 수 있다
|
||||
|
||||
## 목적
|
||||
|
||||
예외를 자기 범주로 번역하는 계층이 무엇을 분류할 수 있는지에 상한이 있다는 것을 잊고, 분기 순서를 고쳐서 해결하려 드는 것을 막는다.
|
||||
|
||||
## 규칙
|
||||
|
||||
1. 분류 가능한 것의 상한은 사슬에 남은 것이다
|
||||
번역 계층은 그 아래 엔진이 버리지 않고 남긴 원인만 볼 수 있다. 사슬에 없는 원인은 어떤 순회 순서로도 분류되지 않는다.
|
||||
|
||||
2. 이름 하나가 여러 엔드포인트로 풀리면 마지막 것만 남을 수 있다
|
||||
Apache HttpClient 5 의 다중 주소 연결 루프는 마지막이 아닌 주소의 실패를 삼킨다. 호스트명이 여러 주소로 풀리면 호출자에게 도달하는 것은 마지막 주소의 오류뿐이다.
|
||||
|
||||
3. 분류를 고치기 전에 입력을 먼저 확인한다
|
||||
잘못된 분류를 보면 분기 순서부터 의심하게 된다. 순서를 바꾸기 전에 사슬을 출력해서 원인이 실제로 거기 있는지 본다.
|
||||
|
||||
4. 테스트는 주소를 고정한다
|
||||
실패 분류를 검증하는 테스트가 호스트명을 쓰면, 그 테스트는 이름 해석이라는 통제되지 않은 변수를 함께 검증한다. 루프백 주소를 직접 쓰거나 주소 패밀리를 고정한다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
이름 하나가 여러 엔드포인트로 풀리는 모든 클라이언트 : DNS A 와 AAAA, 서비스 디스커버리, 다중 브로커 부트스트랩
|
||||
|
||||
실패 분류가 재시도 안전성이나 보안 판정으로 이어지는 곳 : 특히 중요
|
||||
|
||||
## 예외
|
||||
|
||||
모든 주소가 같은 이유로 실패하면 마지막 오류가 대표성을 가지므로 문제가 되지 않는다. 강등은 패밀리별 또는 엔드포인트별 실패 양상이 다를 때만 일어난다.
|
||||
|
||||
## 예시
|
||||
|
||||
인증서가 신뢰 불가면 두 주소 패밀리 모두 TLS 에서 실패하고, 마지막 오류도 핸드셰이크 예외라 분류가 맞는다.
|
||||
|
||||
한 패밀리는 TLS 를 거절하고 다른 패밀리는 연결이 거부되면, 영구 실패가 일시적 연결 실패로 보고된다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **붉은 테스트를 제품 결함으로 읽은 오진**
|
||||
이 규칙을 끌어낸 사례다. 사슬을 출력하기 전까지는 분기 순서가 원인으로 보였다.
|
||||
- **원인 사슬은 가장 구체적인 분류가 이기도록 순회한다**
|
||||
이 규칙과 짝을 이룬다. 그 규칙은 사슬 안에서의 선택을, 이 규칙은 사슬 자체의 한계를 다룬다.
|
||||
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
---
|
||||
kind: REFERENCE
|
||||
slug: retryability-needs-both-idempotency-and-category
|
||||
title: 재시도 가능성은 멱등성과 실패 범주를 함께 봐야 정해진다
|
||||
topic: http-failure-classification
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: reference:retryability-needs-both-idempotency-and-category
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
---
|
||||
|
||||
# 재시도 가능성은 멱등성과 실패 범주를 함께 봐야 정해진다
|
||||
|
||||
## 목적
|
||||
|
||||
한 축만 보고 재시도를 정해서, 비멱등 요청을 반복하거나 영구 실패를 무한히 되풀이하는 것을 막는다.
|
||||
|
||||
## 규칙
|
||||
|
||||
1. 두 입력의 곱이다
|
||||
범주가 재시도 가능하고 동시에 요청이 재시도 안전할 때만 재시도한다. 범주만 보면 비멱등 요청을 재시도하고, 멱등성만 보면 영구 실패를 반복한다.
|
||||
|
||||
2. 전송되지 않았다는 증거는 멱등성 요구를 완화한다
|
||||
아무것도 서버에 닿지 않았음이 증명되면 그 시도는 없던 일이므로 멱등성을 묻지 않아도 된다. 다만 그 증거는 증명일 때만 쓴다.
|
||||
|
||||
3. 증거는 승격하지 않는다
|
||||
일반적인 엔진 입출력 실패를 전송되지 않음으로 올리지 않는다. 모르는 것은 모르는 채로 둔다. 추측해서 올린 판정이 중복 결제를 만든다.
|
||||
|
||||
4. 응답이 전달되기 시작했으면 재시도하지 않는다
|
||||
첫 바이트가 호출자에게 전달된 뒤에는 범주와 무관하게 재시도가 막힌다.
|
||||
|
||||
5. 정책의 화이트리스트로 terminal 판정을 되살리지 않는다
|
||||
화이트리스트는 어떤 범주가 재시도될 수 있는지를 넓히지, 이 실패에 대한 판정을 뒤집지 않는다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
HTTP gRPC 메시징 클라이언트의 재시도 결정
|
||||
|
||||
멱등성 여부가 요청마다 다른 경로
|
||||
|
||||
## 예외
|
||||
|
||||
분류기가 terminal 로 표시한 실패는 정책으로 되살릴 수 없다.
|
||||
|
||||
## 예시
|
||||
|
||||
이 저장소는 전송되지 않음 증거를 별도 축으로 두어 완화를 표현한다. 재시도 컨텍스트에 HTTP 메서드가 없고, 멱등성 키가 전송 여부까지 요구하며, 첫 바이트 전달은 되돌릴 수 없는 래치다.
|
||||
|
||||
부분 응답은 안전하게 멱등인 요청에 대해서만 재시도된다. 그 외에는 원격 결과 불명으로 남긴다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **전송 실패를 단계와 범주 두 축으로 모델링한다**
|
||||
이 규칙의 입력을 만드는 모델이다.
|
||||
- **재시도 안전성은 증거에 기반해 판정한다**
|
||||
이 규칙을 채택한 프로젝트 결정이다.
|
||||
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
---
|
||||
kind: REFERENCE
|
||||
slug: verify-runtime-shape-at-runtime
|
||||
title: 런타임의 모양에 대한 주장은 런타임에서 확인한다
|
||||
topic: http-failure-classification
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: reference:verify-runtime-shape-at-runtime
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
---
|
||||
|
||||
# 런타임의 모양에 대한 주장은 런타임에서 확인한다
|
||||
|
||||
## 목적
|
||||
|
||||
코드를 읽어 얻은 추론이 관측으로 굳어지는 것을 막는다. 특히 실패하는 테스트를 결함의 증거로 읽는 순간을 막는다.
|
||||
|
||||
## 규칙
|
||||
|
||||
1. 벤더 라이브러리의 런타임 동작은 읽어서 알 수 없다
|
||||
이 라이브러리는 예외를 이렇게 감쌀 것이다, 이 게이트가 이 값을 읽을 것이다, 이 경로가 프로덕션 기본값이다. 이런 문장은 전부 가설이며 확인 전까지 판정의 근거가 될 수 없다.
|
||||
|
||||
2. 붉은 테스트는 조사의 시작점이지 결론이 아니다
|
||||
실패하는 테스트는 무언가 어긋났다는 사실만 말한다. 무엇이 어긋났는지는 별도 측정이다. 테스트 이름이 가리키는 대상이 곧 원인인 경우가 오히려 드물다.
|
||||
|
||||
3. 대조는 변수 하나만 바꾼다
|
||||
같은 픽스처에서 호스트 문자열만 바꾸는 식의 대조가 가장 값싸고 결정적이다. 두 가지를 함께 바꾸면 결과를 해석할 수 없다.
|
||||
|
||||
4. 소스를 고치지 않고 확인할 방법이 대개 있다
|
||||
테스트 런타임 클래스패스를 얻어 jshell 로 재현하고, 리플렉션으로 private 메서드를 부르고, 조건만 바꿔 두 번 돌린다. 애플리케이션 소스를 건드리지 않고도 관측이 된다.
|
||||
|
||||
5. 확인 비용은 분 단위다
|
||||
이 프로젝트에서 판정 번복 한 건과 자기 교정 세 건이 전부 이 형태였고, 넷 다 측정 하나로 갈렸다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
프레임워크 드라이버 클라이언트 라이브러리의 런타임 동작에 의존하는 모든 판정
|
||||
|
||||
실패하는 테스트를 근거로 결함을 보고하려는 순간
|
||||
|
||||
관측 없이 심각도를 P1 으로 올리려는 순간
|
||||
|
||||
## 예외
|
||||
|
||||
소스가 저장소 안에 있고 그 경로가 테스트로 고정돼 있으면 읽기로 충분하다. 이 규칙은 벤더 코드의 동작에 대한 것이다.
|
||||
|
||||
## 예시
|
||||
|
||||
분기 순서를 읽고 예외 사슬의 모양을 단정했다. 사슬을 출력하니 달랐다.
|
||||
|
||||
레지스트리에 검사가 없다고 단정했다. 빌드 파일의 주석이 가리키는 세 곳을 따라가니 있었다.
|
||||
|
||||
전역 승인 게이트가 임계값을 읽을 것이라고 가정했다. 그 가드는 해당 필드를 읽지 않았다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **붉은 테스트를 제품 결함으로 읽은 오진**
|
||||
이 규칙을 끌어낸 사례다. 네 건 중 가장 비쌌던 것이다.
|
||||
- **분류기는 엔진이 남긴 것만 볼 수 있다**
|
||||
같은 사례에서 나온 짝 규칙이다. 이쪽은 방법을, 저쪽은 대상을 다룬다.
|
||||
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
---
|
||||
kind: REFERENCE
|
||||
slug: walk-the-cause-chain-most-specific-wins
|
||||
title: 원인 사슬은 가장 구체적인 분류가 이기도록 순회한다
|
||||
topic: http-failure-classification
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: reference:walk-the-cause-chain-most-specific-wins
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
note: 이 규칙을 처음 끌어낸 사례는 나중에 철회되었다. httpclient 의 mTLS 테스트 3건이 실패한 원인은 분기 순서가 아니라 테스트 픽스처의 호스트명이었고, 그 저장소의 분류기는 이 실패의 사례가 아니다. 규칙 자체는 다른 근거로 유효하다.
|
||||
---
|
||||
|
||||
# 원인 사슬은 가장 구체적인 분류가 이기도록 순회한다
|
||||
|
||||
## 목적
|
||||
|
||||
라이브러리가 구체적 원인을 일반적 예외로 감쌌을 때, 바깥 타입만 보고 분류해서 정확한 판정을 잃는 것을 막는다.
|
||||
|
||||
## 규칙
|
||||
|
||||
1. 바깥 타입 하나로 분류하지 않는다
|
||||
Spring 이 엔진 예외를 감싸고 드라이버가 자기 예외를 다시 감싼다. 바깥 타입만 매칭하면 증명 가능한 판정이 모호한 판정으로 내려간다.
|
||||
|
||||
2. 기준은 순서가 아니라 구체성이다
|
||||
이 사슬에서 가장 구체적인 분류가 이기는가를 묻는다. 구현은 두 가지다. 구체적 분기를 앞으로 옮기거나, 사슬 전체를 훑어 최선의 매치를 고른다.
|
||||
|
||||
3. 사이클 안전을 확보한다
|
||||
이미 본 예외를 IdentityHashMap 으로 표시하거나 최대 깊이를 둔다. 순환 참조는 실제로 나타난다.
|
||||
|
||||
4. 벤더별 곁가지를 따라간다
|
||||
getCause 만으로는 부족하다. SQLException 의 getNextException 처럼 벤더가 따로 두는 연결 고리가 있다.
|
||||
|
||||
5. 이 규칙이 실패의 원인이 아닐 수도 있다
|
||||
잘못된 분류를 봤을 때 순서를 의심하기 전에, 그 원인이 사슬에 실제로 있는지 먼저 확인한다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
드라이버나 클라이언트 예외를 자기 범주로 번역하는 모든 분류기
|
||||
|
||||
특히 전송 계층 : 감싸기가 흔하다
|
||||
|
||||
## 예외
|
||||
|
||||
바깥 예외가 실제로 더 구체적인 경우가 있다. 그때는 순서가 아니라 우선순위 표가 필요하고, 그 표를 테스트로 고정해야 한다.
|
||||
|
||||
## 예시
|
||||
|
||||
이 저장소의 mongo 실패 추출기와 bulk 실패 추출기는 IdentityHashMap 으로 방문 표시를 두고 사슬을 훑는다.
|
||||
|
||||
jpa 의 SQL 상태 해석기는 getNextException 을 따라간다.
|
||||
|
||||
httpclient 의 분류기는 사슬 전체를 훑는다. 주석이 이유를 적는다. 감싸인 ConnectException 도 요청이 전송되지 않았음을 증명하므로, 바깥 타입만 매칭하면 증명 가능한 NOT_SENT 를 모호한 SENT_NO_RESPONSE 로 낮추게 된다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **분류기는 엔진이 남긴 것만 볼 수 있다**
|
||||
이 규칙의 상한을 정한다. 사슬에 없는 원인은 어떤 순회로도 분류되지 않는다.
|
||||
- **붉은 테스트를 제품 결함으로 읽은 오진**
|
||||
이 규칙을 잘못 적용한 기록이다.
|
||||
|
||||
Reference in New Issue
Block a user