Files
llm-wiki/raw/blog-topics/idempotency-executor-application-layer-clean-architecture-2026-06-09.md
T

102 lines
4.9 KiB
Markdown

---
title: blog-topic / idempotency-executor-application-layer-clean-architecture-2026-06-09
source_type: blog-topic
status: raw
related_branches: [feature-rate-limit-idempotency-contract]
related_projects: [ca-tmpl]
tags: [blog-topic, ca-tmpl, idempotency, clean-architecture, rate-limit]
created: 2026-06-09
status_label: ready-for-canonical
target_audience: backend-engineer
inspiration_url:
archive_url:
---
# blog-topic: 멱등성을 application layer 실행기로 — Clean Architecture에서 rate-limit/idempotency 운영 계약
> Layer: `raw/blog-topics/` — feature-rate-limit-idempotency-contract 구현에서 나온 글감.
## Parent / 부모
- [[raw/branch-notes/feature-rate-limit-idempotency-contract]]
## 글감 / Topic
운영 멱등성과 rate-limit을 "프레임워크 미들웨어"가 아니라 **계층 소유권**으로 배치한 사례.
## 글감 / Topic seed
- 한 문장 요지: idempotency는 application-layer executor가, rate-limit은 presentation interceptor가 소유하도록 나눠 Clean Architecture 경계를 명시한다.
- 예상 제목 후보:
- 멱등성을 application layer 실행기로 두기
- Clean Architecture에서 idempotency와 rate-limit 소유권 나누기
### 다룰 포인트
1. **owner_layer 분리**: idempotency 코드(409/422)는 `owner_layer: application``IdempotencyExecutor`
포트 + `IdempotencyStore` 포트 + DB 어댑터로 application/infra에 둠. rate-limit(429)은
`owner_layer: presentation` → adapter-web `HandlerInterceptor`. 같은 "운영 횡단 관심사"라도 코드/응답
소유 계층이 다르다.
2. **filter vs interceptor**: rate-limit key가 `IP + uri_template(normalized)`를 요구 →
servlet filter는 handler mapping 이전이라 route template(`/v1/worklogs/{id}`)을 모름.
`HandlerInterceptor`로 옮겨 `BEST_MATCHING_PATTERN_ATTRIBUTE`를 사용.
3. **명시적 실행기 vs AOP**: KEYED use case를 BeanPostProcessor/AOP로 감싸는 대신 `executor.execute(ctx, action, codec)`
명시 호출. 호출부가 약간 장황하지만 ArchUnit/테스트 단순성과 스켈레톤 투명성을 얻음.
4. **insert-or-read + unique 제약을 동시성 중재자로**: 200ms in-flight wait는 IETF 즉시-409 SHOULD의
"운영 친화적 변형"(표준 그대로 아님). fingerprint(SHA-256) mismatch는 IETF 422 권고 정합.
5. **port-codec 분리로 application의 wire-format 중립성**: 실행기는 `IdempotentResponseCodec<R>`(web JSON 소유)로
직렬화만 위임 → application은 transport/storage 중립.
6. **UNSUPPORTED_IMPL_DECISION 정직성**: 200ms·SHA-256·8KB·fixed-window·canonicalization 미적용은
외부 표준이 강제하지 않음을 코드 주석에 명시 — "표준 따름" 과장 금지.
## 관련
- [[wiki/concepts/idempotency-key-design]]
- [[raw/errors/idempotency-expired-row-reclaim-409-loop-2026-06-09]]
- [[raw/errors/scheduled-reaper-wrong-config-prefix-2026-06-09]]
## Canonical 전환 후보 / Canonical extraction candidates
- `wiki/projects/ca-tmpl/idempotency-key-design.md` 후보:
- idempotency executor의 application-layer ownership와 rate-limit presentation ownership 분리.
- 필요한 추가 검증:
- `IdempotencyExecutor`, `IdempotencyStore`, response codec, interceptor 구현 여부.
## 트리거 / Trigger
- 트리거 유형: `branch-work`
- 트리거 날짜: 2026-06-09
- 트리거 연결 노트: [[raw/branch-notes/feature-rate-limit-idempotency-contract]]
## 핵심 주장 후보 / Claim candidates
- 사실 후보:
- idempotency와 rate-limit은 같은 운영 횡단 관심사처럼 보여도 owner layer가 다르다.
- idempotency executor는 application/use case 실행 경계에 놓고, rate-limit은 route template을 아는 web interceptor가 맡는 구조다.
- 의견/해석 후보:
- AOP보다 명시 실행기가 skeleton 투명성과 테스트 용이성을 준다.
## Outline seed
1. owner layer를 application과 presentation으로 나눈다.
2. filter와 interceptor가 route template을 볼 수 있는 시점 차이를 설명한다.
3. `IdempotencyExecutor`와 response codec 분리로 wire format 중립성을 유지한다.
## Sources / 근거 후보
- [[raw/branch-notes/feature-rate-limit-idempotency-contract]]
- [[wiki/concepts/idempotency-key-design]]
- [[raw/errors/idempotency-expired-row-reclaim-409-loop-2026-06-09]]
- [[raw/errors/scheduled-reaper-wrong-config-prefix-2026-06-09]]
## 미해결 / Unknown
- 아직 확인해야 할 사실: 현재 ca-tmpl 코드의 executor/storage/interceptor 구현 여부.
- 과장하면 안 되는 부분: IETF draft 준수와 ca-tmpl의 200ms wait 변형을 섞지 않는다.
## Decision / 처리 결정
- 액션: `promote-to-canonical`
- 이유: `wiki/projects/ca-tmpl/idempotency-key-design.md` 에 application-layer idempotency executor 글감으로 반영했다.
- 다음 단계: target canonical이 아직 `draft` 이므로 `blogify` 전 실제 구현 등급을 재확인한다.