feat: redis, fileserver, httpclient 런타임 시점 구현 추가
This commit is contained in:
@@ -8,9 +8,11 @@
|
||||
추적 ID를 몰라도 읽히도록 결정의 배경과 트레이드오프를 문장으로 풀어 둔다.
|
||||
|
||||
이 계층을 관통하는 큰 원칙 하나: **application-core 는 프레임워크-free 다.** Spring/JPA/HTTP
|
||||
타입을 직접 들이지 않고, 필요한 인프라 능력(트랜잭션·락·인가·알림 등)은 전부 `*Port`
|
||||
인터페이스로 추상화한다. 구현은 adapter 모듈에 있고 컴파일 타임엔 보이지 않는다. 아래 결정
|
||||
대부분이 이 원칙에서 파생된다.
|
||||
타입뿐 아니라 SLF4J/JUL/Logback/Log4j/Micrometer도 직접 들이지 않고, 필요한 인프라 능력
|
||||
(트랜잭션·락·인가·알림·운영 진단 등)은 전부 구체적인 목적의 `*Port` 인터페이스로 추상화한다.
|
||||
구현은 adapter 모듈에 있고 컴파일 타임엔 보이지 않는다. Gradle
|
||||
`verifyApplicationCoreDependencyPurity`와 ArchUnit
|
||||
`APPLICATION_HAS_NO_DIAGNOSTIC_FRAMEWORK`가 이 계약을 자동 검증한다.
|
||||
|
||||
---
|
||||
|
||||
@@ -298,8 +300,8 @@ claim → 트랜잭션 밖에서 발행 → at-least-once 보장.
|
||||
- **fail-closed 계약(I8)**: 발행 실패는 반드시 `RuntimeException` 으로 표면화해야 한다. 구현은
|
||||
예외를 삼키거나 실패 시 log-and-return 하면 안 된다. 일반적인 fail-open 메시징 publisher(잡고
|
||||
로그 후 정상 반환)와의 **의도적·문서화된 차이** 다 — relay 의 Failure condition 이 발행 실패를
|
||||
예외로 관측해야 FAILED/DEAD 전이 + 에러 코드 로그(`OUTBOX_PUBLISH_FAILED` /
|
||||
`OUTBOX_DEAD_LETTER`)를 구동할 수 있기 때문. 삼킨 실패(예외 없음·전이 없음·ERROR 로그 없음)가
|
||||
예외로 관측해야 FAILED/DEAD 전이 + typed failure report를 구동할 수 있기 때문. 삼킨 실패
|
||||
(예외 없음·전이 없음·report 없음)가
|
||||
금지 조건이다 — 행이 영원히 `IN_FLIGHT` 로 남고, aggregate FIFO 큐가 조용히 막히며, 메트릭엔
|
||||
이상이 안 보인다.
|
||||
- **호출 위치**: relay 유스케이스가 **트랜잭션 밖에서** 호출한다. 짧은 `inWrite` 로 배치 claim →
|
||||
@@ -315,13 +317,18 @@ claim → 트랜잭션 밖에서 발행 → at-least-once 보장.
|
||||
FIFO 를 강제한다).
|
||||
3. 각 이벤트를 **트랜잭션 밖에서** 발행하고 결과로 상태 머신을 구동한다:
|
||||
- 발행 성공 → `inWrite { markPublished }` → `PUBLISHED`.
|
||||
- 발행 실패(`RuntimeException`): `attemptCount >= maxAttempts` 면 `markDead` +
|
||||
`OUTBOX_DEAD_LETTER` ERROR 로그; 아니면 `markFailed(nextAttemptAt)` +
|
||||
`OUTBOX_PUBLISH_FAILED` ERROR 로그.
|
||||
- 발행 실패(`RuntimeException`): `attemptCount >= maxAttempts` 면 `markDead`, 아니면
|
||||
`markFailed(nextAttemptAt)`를 먼저 성공시킨 뒤 해당 typed failure report를 보낸다.
|
||||
- **발행 실패는 절대 삼키지 않는다**: relay 는 각 발행 예외를 잡아 FAILED/DEAD 상태 머신을
|
||||
구동하고 ERROR 로그를 낸 뒤 rethrow 하지 않는다(스케줄러 루프가 다음 이벤트로 계속 가야
|
||||
하므로). 모든 발행 실패는 반드시 (a) 상태 전이와 (b) error code·correlationId·eventId·eventType·
|
||||
attemptCount 를 담은 ERROR 로그를 **둘 다** 남긴다. 둘 중 하나라도 빠지면 금지된 silent-swallow.
|
||||
구동하고, 성공한 전이만 `OutboxRelayFailureReportPort`로 보고한 뒤 rethrow 하지 않는다
|
||||
(스케줄러 루프가 다음 이벤트로 계속 가야 하므로). 상태 전이가 실패하면 예외가 전파되고 report는
|
||||
없다. reporter가 `RuntimeException`을 던져도 persisted outcome을 바꾸거나 다음 이벤트를 막지
|
||||
못한다.
|
||||
- **안전한 allowlist report**: `OutboxRelayFailureReport`는
|
||||
`code/eventId/eventType/aggregateId/correlationId/attemptCount/nextAttemptAt/cause`만 가진다.
|
||||
payload, idempotency key, whole `OutboxEvent`, severity/template, arbitrary map은 타입 수준에서
|
||||
전달할 수 없다. retry factory는 `OUTBOX_PUBLISH_FAILED`와 필수 `nextAttemptAt`, dead factory는
|
||||
`OUTBOX_DEAD_LETTER`와 null retry time을 고정한다.
|
||||
- **상태 갱신 실패는 시끄럽게 전파한다**: 발행 성공 후의 `markPublished` 실패는 store/인프라
|
||||
에러지 발행 실패가 아니다. 따라서 FAILED/DEAD 머신을 구동하면 안 된다(이미 전달된 이벤트를
|
||||
dead-letter 하는 꼴). 대신 스케줄러 catch 블록으로 전파되고, 행은 `IN_FLIGHT` 로 남아 고아
|
||||
@@ -372,6 +379,9 @@ claim → 트랜잭션 밖에서 발행 → at-least-once 보장.
|
||||
- `PublishPendingOutboxEventsCommand` — relay 커맨드 마커. 스케줄러 구동이라 caller 파라미터가
|
||||
없고, 모든 운영 파라미터는 생성 시점에 주입된다(IdempotencyExecutor 선례). 호출마다 새 인스턴스를
|
||||
만들 필요가 없게 `INSTANCE` 싱글톤을 제공한다.
|
||||
- `OutboxRelayFailureReportPort` / `OutboxRelayFailureReport` — confirmed FAILED/DEAD 상태를
|
||||
adapter에 전달하는 framework-free outbound contract. 구조화 ERROR 필드와 runbook 렌더링은
|
||||
messaging adapter가 소유한다.
|
||||
|
||||
---
|
||||
|
||||
@@ -462,6 +472,14 @@ application 계층은 락 획득/해제 계약만 알고, 실제 구현은 adapt
|
||||
|
||||
## 로그 가명화 포트 (observability)
|
||||
|
||||
### CorrelationIdPort
|
||||
|
||||
- 현재 application invocation의 correlation id를 `Optional<String>`으로 읽는 framework-free
|
||||
경계다. application/sample use case는 MDC나 SLF4J를 직접 알지 않는다.
|
||||
- inbound web adapter가 sanitized `correlation_id` MDC 슬롯을 구현 세부로 읽는다.
|
||||
- 값이 없거나 blank이면 event publisher는 생성한 event id를 correlation id로 재사용해 기존
|
||||
self-correlation 동작을 유지한다.
|
||||
|
||||
### UserPrincipalPseudonymizerPort
|
||||
|
||||
- raw 보안 principal id 를, 값이 로그/MDC 에 쓰이기 전에 안정적 가명 토큰으로 바꾸는 outbound
|
||||
|
||||
Reference in New Issue
Block a user