--- 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`(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` 전 실제 구현 등급을 재확인한다.