Files
clean-architecture-backend-…/docs/messaging/delivery-guarantees.md
T
DongHyeonka d646c2f12f feat(messaging): 브로커 중립 메시징 플랫폼 24개 leaf 추가
messaging-superpowers-package 설계서/계획서 기반 구현.
registry를 19 → 43 leaf로 확장하고 src/messaging 아래 24개 leaf를 등록.

- core-api: M1 publish/consume + M2 batch·delayed·pause-resume
- policy/transport-spi: 재시도 결정, DLQ orchestration, admission control, lifecycle
- kafka·rabbit(Stable): contiguous commit, confirm/return 상관, 배치, 보안 설정
- pulsar·nats(Experimental): 기본 비활성, live 인증 없음을 코드로 기록
- outbox/inbox/claim-check: 트랜잭션 결합, lease, 무결성 검증
- admin: plan → approve → execute를 타입으로 강제
- 문서 9종, infra compose 7종, JMH 벤치마크 3종

검증: 아키텍처 게이트 3종 통과, 24개 leaf 전부 check 통과,
messaging 테스트 604개 통과/0 실패.

미완: 계획서가 요구한 실 브로커 IT 40개 중 7개만 작성.
Rabbit 13 / Outbox 6 / Inbox 4 / NATS·Pulsar·Share 5 / testkit 2 /
starter·admin 3, 그리고 TLS·ACL 2개가 남음.
2026-08-14 14:55:38 +09:00

76 lines
3.0 KiB
Markdown

# 전달 보장
## 왜 `EXACTLY_ONCE`가 없는가
어떤 브로커도 **외부 side effect를 포함한** exactly-once를 제공하지 않는다.
실제로 존재하는 것은 at-least-once 전달 + 멱등하거나 transactional한 consumer의 조합이다.
플랫폼이 지킬 수 없는 이름을 enum에 두면 그 책임이 눈에 보이지 않는 곳으로 밀려난다.
그래서 `DeliveryGuarantee`는 증거가 끝나는 지점에서 멈춘다.
```java
public enum DeliveryGuarantee { AT_MOST_ONCE, AT_LEAST_ONCE }
```
## Publish 결과는 boolean이 아니다
```java
public enum PublishCompletion { CONFIRMED, REJECTED, AMBIGUOUS }
```
`REJECTED``AMBIGUOUS`를 하나의 "실패"로 합치면 중복 주문이 만들어진다.
전자는 broker가 저장하지 않았음이 **확정**되어 포기해도 안전하고, 후자는 그렇지 않다.
| 상황 | 결과 |
|---|---|
| 로컬 validation 실패 | `REJECTED`, `NOT_TRANSMITTED` |
| broker 명시적 reject / nack | `REJECTED` |
| confirm 수신 | `CONFIRMED` |
| Rabbit confirm + unroutable return | `REJECTED`, `UNROUTABLE` |
| bytes 전송 후 connection loss | `AMBIGUOUS` |
| confirm timeout | `AMBIGUOUS` |
| adapter가 판정 불가 | 보수적으로 `AMBIGUOUS` |
`PublishResult` 생성자가 이 규칙을 강제한다. `CONFIRMED`인데 broker acceptance가 없거나,
`AMBIGUOUS`인데 confirmation level을 주장하면 **객체 생성 자체가 실패**한다.
## Ordering
```java
public enum OrderingScope { NONE, DESTINATION, PARTITION, KEY }
```
순서는 partition·key·단일 consumer의 성질이지 destination 전체의 성질이 아니다.
`GLOBAL`이 없는 이유가 이것이다.
`DestinationProfileValidator`가 다음을 거부한다.
- `ordering=KEY`인데 key resolver 없음
- ordered destination인데 `ALLOW_REORDER` retry
- `orderingImpact=PRESERVE`인데 재발행형 retry(`RETRY_DESTINATION`, `BROKER_DELAYED`)
- `ordering=DESTINATION`인데 concurrency > 1
- ordered destination인데 ordering unit당 in-flight > 1
## External side effect
```java
public enum ExternalSideEffectGuarantee { NONE, IDEMPOTENCY_REQUIRED, INBOX_TRANSACTIONAL }
```
`INBOX_TRANSACTIONAL`만이 "DB side effect와 중복 차단이 같은 transaction에서 commit된다"를 의미한다.
Kafka transaction은 **Kafka 안에서만** 원자적이므로 이 값과 함께 설정하면
`KafkaTransactionProfileValidator`가 거부한다. 두 개의 독립적인 commit을 하나로 착각하게 두지 않기 위해서다.
## Consumer settlement 순서
```text
RECEIVED → DECODING → PROCESSING → HANDLER_SUCCEEDED → SETTLEMENT_SENDING
├→ SETTLED
└→ SETTLEMENT_UNKNOWN
```
- handler는 broker ACK API를 호출하지 않는다.
- `Success` 이후에만 source settlement한다.
- `SETTLEMENT_UNKNOWN`은 성공이 아니다. redelivery 가능성을 의미한다.
- `SettlementResult` 생성자가 `SETTLED`인데 `redeliveryPossible=true`인 조합을 거부한다.