Files
clean-architecture-backend-…/messaging-superpowers-package/docs/superpowers/specs/2026-08-10-messaging-platform-design.md
T

73 KiB
Raw Blame History

Messaging Platform 설계서

  • 상태: 구현 승인용 기준 설계
  • 작성일: 2026-08-10
  • 대상: Java/Spring Backend Skeleton의 공통 Messaging Capability
  • 근거 자료: Java/Spring Messaging 플랫폼 심층 리서치
  • 문서 역할: 구현 중 추가 설계 판단이 발생하지 않도록 공개 계약, 브로커별 보장, 오류·재시도·DLQ·Outbox·Inbox·운영·검증 경계를 고정한다.

1. 요약

이 플랫폼은 send()consume()을 편하게 감싸는 라이브러리가 아니다. 메시지 생성부터 브로커 수락, 복제·영속화, 소비자 전달, 처리, settlement, 재전달, DLQ, replay·redrive까지의 신뢰성 증거와 실패 의미론을 통제하는 백엔드 기반 모듈이다.

핵심 설계는 다음과 같다.

영역 확정 결정
공통 구조 자체 messaging-core가 공개 계약을 소유하고 브로커별 Native Adapter가 구현한다.
기본 API M1 Typed Publisher·Handler를 일반 서비스의 기본 진입점으로 사용한다.
고급 API M2 Batch·Manual Settlement·Pause/Resume·Delayed·Replay를 별도 권한으로 제공한다.
Native 기능 M3에서 Kafka transaction·partition, Rabbit routing·quorum, Pulsar subscription, NATS subject를 제한적으로 제공한다.
운영 기능 M4 Admin Plane에서 topology 검증, replay, redrive, offset reset, purge·delete를 제공한다.
전달 보장 AT_MOST_ONCE, AT_LEAST_ONCE만 공통 보장으로 제공한다.
Exactly-once 공통 EXACTLY_ONCE 옵션을 두지 않는다. 브로커 transaction 범위와 Inbox·Outbox 조합을 각각 명시한다.
publish 결과 성공·실패를 boolean으로 축소하지 않고 CONFIRMED, REJECTED, AMBIGUOUS와 broker evidence를 함께 반환한다.
소비자 기본 중복 전달을 정상적인 failure mode로 간주하고 handler 성공 뒤에만 settlement한다.
DLQ DLQ publish가 confirm된 뒤 source를 settlement한다. DLQ publish 실패 시 source를 ACK하지 않는다.
Kafka Kafka 4.2+를 Stable 기준으로 하고 4.3.x에서 검증한다. Share Group은 플랫폼 Experimental이다.
RabbitMQ RabbitMQ 4.3.x를 Stable 기준으로 하고 durable work queue는 quorum queue를 기본값으로 한다.
Pulsar 4.0 LTS와 4.2 호환성을 Experimental Adapter에서 검증한다.
NATS JetStream 2.14.x를 Experimental Adapter에서 검증한다. Core NATS는 Stable reliability 경로에 사용하지 않는다.
Reliability Transactional Outbox, Inbox, Idempotent Consumer, Claim Check를 별도 Reliability 모듈로 제공한다.
Schema JSON Stable, Avro·Protobuf 선택 Stable, Raw Bytes M2, Java Serialization 비지원이다.
CloudEvents Domain·Integration Event에 선택 가능한 1.0.2 compatible profile을 제공한다.
topology 개발·테스트는 선택적 자동 생성, 운영은 IaC 생성 + 애플리케이션 startup 검증이 기본이다.
payload portability 기본 상한은 1 MiB이며 초과 payload는 Claim Check를 사용한다.
관측성 논리 메시지와 물리 delivery·attempt를 분리해 측정하고 고카디널리티 값과 payload를 기록하지 않는다.

플랫폼의 완료 기준은 모든 브로커를 같아 보이게 만드는 것이 아니다. 다음 질문에 adapter가 동일한 형식으로 답할 수 있어야 한다.

publish가 실패했다.
→ broker에 전송되지 않았음을 아는가?
→ broker가 저장했을 가능성이 있는가?
→ 어떤 수준의 confirm을 받았는가?
→ 같은 logical message ID로 다시 시도해도 되는가?

consume이 실패했다.
→ handler가 시작됐는가?
→ side effect가 commit됐는가?
→ settlement가 broker에 반영됐는가?
→ redelivery가 발생할 수 있는가?

retry·DLQ·redrive를 수행한다.
→ 원래 message identity가 보존되는가?
→ ordering이 깨지는가?
→ duplicate side effect를 Inbox가 차단하는가?

2. 목표와 성공 기준

2.1 목표

  1. 일반 서비스가 브로커 종류를 몰라도 typed message를 publish·consume할 수 있게 한다.
  2. 브로커별 보장 차이를 숨기지 않고 capability와 evidence로 노출한다.
  3. publish 결과가 확정적이지 않으면 AMBIGUOUS로 표현한다.
  4. consumer duplicate, ACK 유실, rebalance, failover를 정상적인 운영 시나리오로 다룬다.
  5. retry·DLQ·redrive에서 logical messageId와 schema identity를 보존한다.
  6. Kafka와 RabbitMQ를 Stable Adapter로 제공한다.
  7. Pulsar와 NATS JetStream을 Experimental Adapter로 제공해 abstraction boundary를 검증한다.
  8. Outbox·Inbox를 실행 가능한 Reliability Recipe로 제공한다.
  9. topology, 보안, 관측성, 장애·성능 검증을 코드와 CI에서 강제한다.

2.2 성공 기준

  • Core API에 broker SDK 타입과 Spring Message<?>가 노출되지 않는다.
  • Core API에 EXACTLY_ONCE enum이나 boolean 옵션이 존재하지 않는다.
  • 모든 publish는 PublishResult 또는 안정 예외를 반환하며 AMBIGUOUS를 구분한다.
  • Kafka publish는 idempotent producer와 acks=all을 Stable profile에서 강제한다.
  • Rabbit publish는 publisher confirm과 routing outcome을 분리한다.
  • M1 consumer는 handler 성공 이전에 source settlement를 수행하지 않는다.
  • retry destination 또는 DLQ publish가 실패하면 source message를 settlement하지 않는다.
  • strict ordering destination에는 reorder 가능한 retry 전략을 설정할 수 없다.
  • Outbox relay가 ambiguous publish를 다시 시도해도 같은 messageId를 사용한다.
  • Inbox record와 business side effect가 같은 DB transaction에서 commit된다.
  • 일반 애플리케이션 credential로 replay, redrive, purge, delete, offset reset을 호출할 수 없다.
  • 동일 Core Contract Suite가 Kafka와 RabbitMQ에서 통과한다.
  • Experimental Adapter가 Core 계약을 변경하지 않고 Pulsar·NATS의 차이를 capability로 표현한다.
  • metric label과 log에 payload, secret header, 실제 message ID, 동적 key가 노출되지 않는다.

3. 입력 자료의 제약과 구현 가정

근거 자료에는 플랫폼 의미론과 지원 경계가 충분히 정의되어 있으나 실제 Backend Skeleton 저장소 구조와 dependency catalog는 포함되어 있지 않다. 구현 계획은 다음 가정을 사용한다.

항목 구현 가정
언어 Java 21
빌드 Gradle Kotlin DSL
root package io.backend.skeleton.messaging
모듈 root modules/messaging
Spring 버전 host 저장소의 dependency management를 사용하고 Spring Framework 6.2·7.0 호환 job을 둔다.
Core 비동기 타입 CompletionStage
Reactive facade 별도 Reactor adapter에서 Mono·Flux로 제공
DB recipe PostgreSQL 16 + Spring JDBC/JPA + Flyway
JSON Jackson
Avro·Protobuf 선택 모듈
관측성 Micrometer + OpenTelemetry exporter adapter
테스트 JUnit 5, AssertJ, ArchUnit, Testcontainers, Toxiproxy
코드 생성 사용하지 않는다. Schema code generation은 각 Avro·Protobuf 모듈에 한정한다.

실제 저장소 경로가 다르면 파일 경로만 기계적으로 매핑한다. 공개 타입, 모듈 의존 방향, 상태·오류·보장 계약은 변경하지 않는다.

다음 숫자는 플랫폼 starter의 초기 안전 기본값이다. 배포 profile에서 더 작게 조정할 수 있고, 더 크게 조정하려면 명시적 override와 성능 인증이 필요하다.

설정 기본값
logical payload 최대 크기 1,048,576 bytes
global hard payload 최대 크기 8,388,608 bytes
header 총 크기 32,768 bytes
header 개수 64
header key UTF-8 크기 128 bytes
header value UTF-8 크기 4,096 bytes
publish operation 기본 timeout 5초
consumer handler 기본 timeout 30초
graceful shutdown drain 30초
일반 destination 기본 retry 횟수 0회, 명시 profile만 허용
DLQ redrive batch 100건
Outbox relay batch 100건
Outbox lease 30초
Outbox polling 500ms

4. 범위

4.1 Core 포함 범위

Async Command
Domain Event
Integration Event
Work Queue
PublishSubscribe
Event Stream
Typed Publish
Typed Handler
Batch Publish
Batch Consume
Manual Settlement
Pause / Resume
Delayed Delivery capability
Retry
DLQ / Parking
Replay / Redrive
Schema / Serialization
Broker Capability
Topology Validation
Security / Observability

4.2 별도 Reliability 모듈

Transactional Outbox
Inbox
Idempotent Consumer
Claim Check
Debezium Outbox Event Router integration

4.3 별도 기술 모듈

Email·SMS·Push               → notification
Browser live connection      → websocket
Redis Stream·Pub/Sub command → redis
Binary payload storage       → objectstorage / fileserver
CDC engine 자체              → connector/CDC
Saga orchestration           → 별도 orchestration

4.4 명시적 비지원

  • 공통 EXACTLY_ONCE 설정
  • 전역 순서 보장
  • DB와 broker의 자동 원자 transaction 보장
  • 기본 XA
  • Java native serialization
  • 무제한 payload·header
  • 무한 retry
  • 운영 application의 topology 파괴 작업
  • 일반 애플리케이션에 raw broker client 반환
  • DLQ publish 확인 전 source ACK
  • one-shot payload의 자동 publish retry
  • replay·redrive를 일반 consumer API로 공개

5. 핵심 설계 원칙

  1. 증거를 보장 이름보다 우선한다. BROKER_ACK, REPLICATION_OR_PERSISTENCE_ACK, routing result를 별도 필드로 표현한다.
  2. 모호성을 숨기지 않는다. 전송 후 confirm을 받지 못하면 성공 또는 실패로 추정하지 않고 AMBIGUOUS를 반환한다.
  3. 중복 전달을 기본 전제로 둔다. consumer는 handler·DB·settlement 경계에서 duplicate-safe해야 한다.
  4. logical identity를 유지한다. publish retry, redelivery, DLQ, redrive에서 messageId를 바꾸지 않는다.
  5. ordering scope를 제한한다. 순서는 destination 전체가 아니라 partition·key·single consumer 같은 실제 범위로만 표현한다.
  6. 공통 API가 최소공배수가 되지 않게 한다. 공통 계약과 broker-native capability를 분리한다.
  7. 정책 우회 경로를 만들지 않는다. M2·M3도 TLS, credential, destination ACL, payload limit, trace, masking을 우회할 수 없다.
  8. 운영 변경은 Admin Plane에서 수행한다. 애플리케이션 runtime은 describe·validate만 수행한다.
  9. 자동 retry는 opt-in이다. 실패 분류, ordering, idempotency, deadline, broker capability가 모두 허용할 때만 수행한다.
  10. DLQ도 publish다. DLQ publish 실패를 source success로 처리하지 않는다.
  11. Outbox는 duplicate를 제거하지 않는다. stable message ID와 Inbox가 함께 있어야 effectively-once DB effect를 구성한다.
  12. Stable과 Experimental을 코드로 분리한다. Experimental adapter가 Stable module dependency graph에 자동 유입되지 않는다.

6. 지원 매트릭스

6.1 브로커

브로커 등급 인증 기준 Stable 기능 제한·Experimental
Kafka Stable 4.2+ / 4.3.x producer, idempotence, consumer group, batch, pause/resume, replay, transaction capability Share Group
RabbitMQ Stable 4.3.x exchange, routing, confirm, mandatory return, manual ACK, quorum queue, retry, DLQ stream·특수 plugin
Pulsar Experimental 4.0 LTS + 4.2 typed publish/consume, Shared, Key_Shared, schema transaction·full compatibility 승격 전
NATS JetStream Experimental 2.14.x stream, durable consumer, explicit ACK, dedupe, replay platform-managed DLQ workflow
Artemis/JMS Extension 현재 Stable 범위 제외 adapter SPI만 별도 ADR과 Contract Suite 통과 뒤 지원 선언
Cloud broker 별도 Adapter provider별 Core 변경 없이 adapter capability IAM·visibility·ordering provider semantics

6.2 기능 등급

기능 등급
Typed Publish·Consume Stable M1
At-least-once contract Stable
Async publish result Stable
Ambiguous publish Stable
Auto settlement after success Stable M1
Batch M2
Manual settlement M2
RequestReply M2, 제한적
Delayed·Scheduled Capability-specific M2
Replay M2 + Admin
Redrive Admin
Broker transaction M3 native capability
Kafka Share Group Experimental M2/M3
Pulsar·NATS adapters Experimental
Spring Cloud Stream bridge Optional integration

7. 전체 아키텍처

Application
    │
    ├─ M1 Typed Messaging API
    │    ├─ MessagePublisher
    │    └─ MessageHandler
    │
    ├─ M2 Advanced API
    │    ├─ BatchPublisher
    │    ├─ ManualSettlementHandler
    │    ├─ PauseResumeController
    │    └─ ReplayRequest
    │
    ├─ M3 Native Capability
    │    ├─ KafkaNativeCapability
    │    ├─ RabbitNativeCapability
    │    ├─ PulsarNativeCapability
    │    └─ NatsNativeCapability
    │
    └─ M4 Admin Plane
         ├─ TopologyValidator
         ├─ ReplayService
         ├─ RedriveService
         └─ DestructiveOperationGuard

Policy Pipeline
    ├─ Envelope Validation
    ├─ Destination Resolution
    ├─ Schema Encoding
    ├─ Security / ACL
    ├─ Payload / Header Limit
    ├─ Retry / DLQ Policy
    ├─ Observability
    └─ Transport Adapter

Transport Adapters
    ├─ Kafka Stable
    ├─ RabbitMQ Stable
    ├─ Pulsar Experimental
    └─ NATS JetStream Experimental

Reliability
    ├─ Outbox
    ├─ Inbox
    ├─ Idempotent Consumer
    └─ Claim Check

7.1 Publish 흐름

Typed payload
→ MessageEnvelope 생성·검증
→ logical destination profile 조회
→ schema encoding
→ reserved header mapping
→ payload/header limit
→ broker adapter publish
→ broker evidence 수집
→ PublishResult 변환
→ metric·trace·audit 종료

7.2 Consume 흐름

broker delivery
→ transport metadata 추출
→ envelope/header 검증
→ schema decode
→ MessageDelivery 생성
→ handler 실행
→ success: source settlement
→ retry: policy에 따른 inline/pause/retry destination
→ permanent: confirmed DLQ publish 후 source settlement
→ settlement 결과·redelivery·lag 관측

8. 모듈 구조

modules/messaging/
├── messaging-core-api
├── messaging-schema-api
├── messaging-schema-json
├── messaging-schema-avro
├── messaging-schema-protobuf
├── messaging-cloudevents
├── messaging-policy
├── messaging-transport-spi
├── messaging-observability
├── messaging-security
├── messaging-kafka
├── messaging-kafka-share-experimental
├── messaging-rabbit
├── messaging-reliability-api
├── messaging-outbox-jpa
├── messaging-inbox-jpa
├── messaging-claim-check
├── messaging-admin-api
├── messaging-admin-runtime
├── messaging-pulsar-experimental
├── messaging-nats-experimental
├── messaging-spring-cloud-stream-bridge
├── messaging-spring-boot-starter
└── messaging-testkit

8.1 의존 방향

core-api
  ↑
policy / schema-api / transport-spi / observability / security
  ↑
kafka / rabbit / pulsar / nats
  ↑
spring-boot-starter

reliability-api
  ↑
outbox-jpa / inbox-jpa / claim-check

admin-api
  ↑
admin-runtime + broker adapter admin capability

금지 의존은 다음과 같다.

  • messaging-core-api → Spring Kafka, Spring AMQP, Pulsar, NATS
  • messaging-core-api → Spring Message<?>
  • Stable module → Experimental module
  • application API → M4 destructive implementation
  • Reliability core → 특정 broker SDK

9. 공개 API 등급

등급 사용 대상 제공 기능 금지 사항
M1 일반 업무 모듈 typed publish, typed handler, 자동 settlement topic·offset·channel·delivery tag 직접 조작
M2 승인된 고급 모듈 batch, manual settlement, delayed, pause/resume, replay request credential·payload limit·ACL 우회
M3 broker 특화 모듈 partition, routing, native transaction, subscription raw client 반환, topology 파괴
M4 운영자·관리 서비스 topology, offset/cursor, redrive, purge/delete application credential 사용

M2와 M3는 @RequiresMessagingCapability 또는 명시적 bean qualifier를 통해서만 주입된다. M4는 별도 Spring Boot application 또는 별도 security chain과 credential을 사용한다.


10. 핵심 식별자와 타입

public record MessageId(UUID value) {
    public static MessageId newId() {
        return new MessageId(UuidV7.next());
    }
}

public record MessageType(String value) {}
public record SchemaVersion(int value) {}
public record ProducerId(String value) {}
public record DestinationName(String value) {}
public record CorrelationId(String value) {}
public record CausationId(MessageId value) {}
public record RedriveId(UUID value) {}
public record ReplayId(UUID value) {}

10.1 식별자 규칙

  • messageId는 logical message identity다.
  • publish retry, broker redelivery, retry destination, DLQ, redrive에서 유지한다.
  • 새 업무 사건 또는 새 command일 때만 새 messageId를 생성한다.
  • redriveId, replayId, transportAttemptId는 별도 식별자다.
  • Java class name을 messageType으로 사용하지 않는다.
  • messageTypeorder.created, payment.capture.requested와 같은 안정된 catalog 값이다.
  • 실제 messageId는 metric label에 사용하지 않는다.

11. Message Envelope

public record MessageEnvelope<T>(
        MessageId messageId,
        MessageType messageType,
        SchemaVersion schemaVersion,
        Instant producedAt,
        Optional<Instant> occurredAt,
        ProducerId producer,
        Optional<CorrelationId> correlationId,
        Optional<CausationId> causationId,
        ContentType contentType,
        Optional<String> partitionKey,
        Optional<String> orderingKey,
        Optional<TenantContext> tenantContext,
        TraceContext traceContext,
        MessageHeaders headers,
        T payload) {
}

11.1 필드 정책

필드 정책
messageId 필수, UUIDv7
messageType 필수, catalog 등록
schemaVersion 1 이상
producedAt platform이 설정
occurredAt Event 필수, Command 선택
producer logical service ID
correlationId workflow 단위
causationId 선행 message가 있을 때
contentType codec registry 값
partitionKey distribution 목적
orderingKey ordering 의도
tenantContext allowlist된 bounded value
traceContext platform이 생성·전파
headers 제한형 immutable map
payload null 금지. Kafka tombstone은 M3 전용 타입

11.2 Header 정책

public final class MessageHeaders {
    private final Map<HeaderName, HeaderValue> values;

    public static MessageHeaders of(Map<HeaderName, HeaderValue> values) {
        return HeaderPolicy.validateAndCopy(values);
    }
}

예약 header는 platform만 쓸 수 있다.

msg.id
msg.type
msg.schema-version
msg.producer
msg.produced-at
msg.occurred-at
msg.correlation-id
msg.causation-id
msg.content-type
msg.partition-key
msg.ordering-key
msg.redrive-id
msg.redrive-count
traceparent
tracestate
baggage

금지 header는 다음과 같다.

Authorization
Proxy-Authorization
Cookie
Set-Cookie
access_token
refresh_token
api_key
password
client_secret

12. 논리 Destination과 Profile

public record MessageDestination<T>(
        DestinationName name,
        MessageType messageType,
        Class<T> payloadType) {
}

애플리케이션은 topic, exchange, queue, subject가 아니라 logical destination을 사용한다. 물리 매핑은 DestinationProfile이 소유한다.

messaging:
  destinations:
    order-events:
      broker: kafka-primary
      kind: EVENT_STREAM
      physical:
        topic: order.events.v1
      schema:
        codec: json
        compatibility: BACKWARD_TRANSITIVE
      guarantees:
        delivery: AT_LEAST_ONCE
        ordering: KEY
        external-side-effect: INBOX_TRANSACTIONAL
      producer:
        confirmation: REPLICATION_OR_PERSISTENCE_ACK
        timeout: 5s
      consumer:
        group: order-projection
        concurrency: 6
        max-in-flight-per-partition: 1
        handler-timeout: 30s
      retry:
        mode: PAUSE_PARTITION
        max-attempts: 3
        initial-delay: 200ms
        max-delay: 2s
        ordering-impact: PRESERVE
      dlq:
        destination: order-events-dlq
      payload:
        max-bytes: 1048576

    email-work:
      broker: rabbit-primary
      kind: WORK_QUEUE
      physical:
        exchange: notification.work
        routing-key: email
        queue: notification.email.q
      guarantees:
        delivery: AT_LEAST_ONCE
        ordering: NONE
        external-side-effect: IDEMPOTENCY_REQUIRED
      producer:
        confirmation: REPLICATION_OR_PERSISTENCE_ACK
        mandatory-routing: true
        timeout: 5s
      consumer:
        concurrency: 8
        prefetch: 16
        handler-timeout: 30s
      retry:
        mode: RETRY_DESTINATION
        max-attempts: 5
        initial-delay: 1s
        max-delay: 5m
        ordering-impact: ALLOW_REORDER
      dlq:
        destination: email-work-dlq
      payload:
        max-bytes: 1048576

12.1 Startup validation

다음 조건이면 애플리케이션 시작을 실패시킨다.

  • AT_LEAST_ONCE인데 adapter가 confirm 또는 explicit settlement를 제공하지 못함
  • ordering-impact=PRESERVE인데 retry destination 전략 사용
  • ordering=KEY인데 key resolver 없음
  • M1 destination에 manual settlement 설정
  • payload hard limit 초과
  • DLQ가 자기 자신을 가리킴
  • retry destination cycle 존재
  • 운영 profile에서 topology auto-create 활성화
  • production에서 TLS 또는 broker 인증 비활성
  • Kafka Stable producer에서 idempotence 또는 acks=all 비활성
  • Rabbit durable work queue에서 classic transient queue 사용
  • schema codec 또는 message type catalog 누락

13. Capability Model

공통 API는 브로커가 모든 기능을 동일하게 지원한다고 가정하지 않는다.

public record MessagingCapabilities(
        boolean brokerAcknowledgement,
        boolean replicationOrPersistenceEvidence,
        boolean perMessageSettlement,
        boolean batchSettlement,
        boolean orderedStream,
        boolean keyedOrdering,
        boolean replay,
        boolean delayedDelivery,
        boolean brokerTransaction,
        boolean deduplicatedPublish,
        boolean nativeDeadLetter,
        boolean topologyManagement) {
}

실행 시에는 destination별로 capability snapshot을 만든다.

public interface CapabilityRegistry {
    DestinationCapabilities capabilities(DestinationName destination);
}

13.1 Capability 검증 규칙

요청 기능 필수 capability
confirmation=REPLICATION_OR_PERSISTENCE_ACK adapter가 해당 증거를 입증
manual settlement perMessageSettlement 또는 명시적 batch settlement
replay replay
broker transaction brokerTransaction
strict key ordering keyedOrdering + concurrency 제약
delayed delivery native 또는 retry destination 구현
native DLQ native dead-letter 보장 조건 충족
deduplicated publish broker dedupe + stable message ID

Capability가 없으면 조용히 기능을 약화하지 않고 startup error 또는 MessagingCapabilityUnavailableException을 반환한다.


14. Schema·직렬화 계약

14.1 Codec API

public interface MessageCodec {
    ContentType contentType();

    EncodedMessage encode(
            MessageType type,
            SchemaVersion version,
            Object payload);

    <T> T decode(
            MessageType type,
            SchemaVersion version,
            EncodedMessage encoded,
            Class<T> payloadType);
}

public record EncodedMessage(
        byte[] bytes,
        ContentType contentType,
        Optional<SchemaReference> schemaReference) {
}

14.2 지원 수준

형식 등급 정책
JSON Stable 기본 Jackson allowlist, 깊이·크기 제한
Avro 선택 Stable Registry profile과 compatibility gate
Protobuf 선택 Stable generated type + unknown field 정책
JSON Schema Registry profile JSON codec와 연동
Raw Bytes M2 schema 검증 우회 감사
Java Serialization 비지원 wire compatibility·보안 문제

14.3 Compatibility

BACKWARD
BACKWARD_TRANSITIVE
FORWARD
FORWARD_TRANSITIVE
FULL
FULL_TRANSITIVE
NONE_EXPERIMENTAL

운영 기본값은 다음과 같다.

  • integration event: BACKWARD_TRANSITIVE
  • 장수 공용 event: FULL_TRANSITIVE 검토
  • command: producer·consumer 동시 배포가 가능하면 BACKWARD
  • schema 없음: M2 Raw Bytes에서만 허용

14.4 Schema 변경 규칙

  • optional/default 없는 필드 추가를 금지한다.
  • 필드 rename은 add → dual read/write → remove로 처리한다.
  • enum에는 unknown value 전략이 있어야 한다.
  • message type 변경은 새 type으로 취급한다.
  • 역직렬화 실패는 일반 transient retry 대상이 아니다.
  • Kafka tombstone은 nullable payload로 일반화하지 않고 KafkaTombstonePublish M3 API로 분리한다.

14.5 Golden Message

각 message type은 다음 fixture를 보유한다.

schemas/<message-type>/v1/valid.*
schemas/<message-type>/v2/valid.*
schemas/<message-type>/invalid/*

CI에서 구버전 fixture를 최신 consumer가 읽고, compatibility mode에 따라 신버전 fixture를 구버전 consumer가 읽는지 검증한다.


15. CloudEvents Profile

CloudEvents는 Domain·Integration Event의 상호운용 profile로만 제공한다. Command와 Work Item에는 강제하지 않는다.

public interface CloudEventMapper {
    CloudEvent toCloudEvent(MessageEnvelope<?> envelope, URI source);
    MessageEnvelope<EncodedMessage> fromCloudEvent(CloudEvent event);
}

매핑은 다음과 같다.

Envelope CloudEvents
messageId id
producer/source source
messageType type
occurredAt time
contentType datacontenttype
schema URI dataschema
correlationId extension correlationid
causationId extension causationid
schemaVersion extension schemaversion
tenantContext extension tenantcontext

Kafka binary binding을 사용할 때 tombstone과 empty event를 구분한다. CloudEvent data가 없다는 이유로 Kafka null value를 자동 생성하지 않는다.


16. Publisher 공개 계약

public interface MessagePublisher {
    <T> CompletionStage<PublishResult> publish(
            MessageDestination<T> destination,
            MessageEnvelope<T> message,
            PublishOptions options);
}

public record PublishOptions(
        Duration timeout,
        ConfirmationRequirement confirmation,
        Optional<PublishDeduplication> deduplication,
        Map<String, String> brokerHints) {
}

M1에서는 brokerHints가 비어 있어야 한다. M3 adapter만 typed hint를 추가할 수 있다.

16.1 Blocking·Reactive facade

public interface BlockingMessagePublisher {
    <T> PublishResult publish(
            MessageDestination<T> destination,
            MessageEnvelope<T> message,
            PublishOptions options);
}

public interface ReactiveMessagePublisher {
    <T> Mono<PublishResult> publish(
            MessageDestination<T> destination,
            MessageEnvelope<T> message,
            PublishOptions options);
}

Core 구현은 CompletionStage를 사용하고 Blocking·Reactive facade가 lifecycle과 cancellation을 변환한다.

16.2 Batch Publish

public interface BatchMessagePublisher {
    CompletionStage<BatchPublishResult> publish(
            List<PublishRequest<?>> requests,
            BatchPublishOptions options);
}

Batch는 transaction이 아니다. 결과는 입력 index별 성공·실패·모호성을 유지한다.

public record BatchPublishResult(
        List<BatchPublishItemResult> items,
        Duration elapsed) {
}

자동 batch retry는 하지 않는다. 호출자가 duplicate-safe한 item만 같은 messageId로 다시 제출한다.


17. Publish 결과와 증거

17.1 결과 모델

public record PublishResult(
        PublishCompletion completion,
        PublishEvidence evidence,
        RoutingOutcome routingOutcome,
        Optional<BrokerPosition> position,
        int attempts,
        Duration elapsed,
        Optional<FailureDescriptor> failure) {
}

public enum PublishCompletion {
    CONFIRMED,
    REJECTED,
    AMBIGUOUS
}

public record PublishEvidence(
        boolean queuedLocally,
        TransmissionEvidence transmission,
        boolean brokerAccepted,
        ConfirmationLevel confirmationLevel) {
}

public enum TransmissionEvidence {
    NOT_TRANSMITTED,
    MAY_HAVE_BEEN_TRANSMITTED,
    TRANSMITTED
}

public enum ConfirmationLevel {
    NONE,
    BROKER_ACK,
    REPLICATION_OR_PERSISTENCE_ACK
}

public enum RoutingOutcome {
    NOT_APPLICABLE,
    ROUTED,
    UNROUTABLE,
    UNKNOWN
}

17.2 판정 규칙

상황 결과
로컬 validation 실패 REJECTED, NOT_TRANSMITTED
broker 명시적 reject REJECTED
confirm 수신 CONFIRMED
Rabbit confirm + unroutable return REJECTED, UNROUTABLE
bytes 전송 후 connection loss AMBIGUOUS
confirm timeout AMBIGUOUS
broker ACK 수준이 요구보다 약함 REJECTED 또는 startup validation failure
adapter가 증거를 판정할 수 없음 보수적으로 AMBIGUOUS

17.3 Broker Position

public interface BrokerPosition {
    String broker();
    Map<String, String> diagnosticAttributes();
}

public record KafkaPosition(
        String topic,
        int partition,
        long offset) implements BrokerPosition {
    @Override public String broker() { return "kafka"; }
    @Override public Map<String, String> diagnosticAttributes() {
        return Map.of("topic", topic, "partition", Integer.toString(partition),
                "offset", Long.toString(offset));
    }
}

public record RabbitPublishReference(
        String exchange,
        String routingKey,
        long sequence) implements BrokerPosition {
    @Override public String broker() { return "rabbitmq"; }
    @Override public Map<String, String> diagnosticAttributes() {
        return Map.of("exchange", exchange, "routingKey", routingKey,
                "sequence", Long.toString(sequence));
    }
}

Position은 진단·replay에 쓰며 공통 업무 로직의 분기 기준으로 사용하지 않는다.


18. Producer 상태 머신

CREATED
  ↓
VALIDATED
  ↓
ENCODED
  ↓
QUEUED_LOCALLY
  ↓
TRANSMITTING
  ├─→ REJECTED
  ├─→ AMBIGUOUS
  ↓
BROKER_ACCEPTED
  ├─→ CONFIRMED
  └─→ AMBIGUOUS

상태 머신은 내부 trace·failure evidence에 사용한다. 공개 결과에는 최종 completion과 evidence만 노출한다.

18.1 자동 publish retry

Core publisher의 기본은 자동 retry 없음이다. adapter 내부 protocol retry는 다음 조건에서만 허용한다.

  • 동일 messageId 유지
  • broker-native dedup 또는 producer idempotence 활성
  • payload replayable
  • confirmation timeout 전체 예산 내
  • adapter가 duplicate risk를 문서화

AMBIGUOUS 결과를 새 messageId로 자동 재발행하지 않는다.


19. Consumer 공개 계약

public interface MessageHandler<T> {
    CompletionStage<HandleResult> handle(MessageDelivery<T> delivery);
}

public record MessageDelivery<T>(
        MessageEnvelope<T> message,
        DeliveryMetadata metadata,
        DeliveryContext context) {
}

public sealed interface HandleResult
        permits HandleResult.Success,
                HandleResult.Retry,
                HandleResult.DeadLetter,
                HandleResult.Reject {

    record Success() implements HandleResult {}

    record Retry(FailureDescriptor failure) implements HandleResult {}

    record DeadLetter(FailureDescriptor failure) implements HandleResult {}

    record Reject(FailureDescriptor failure) implements HandleResult {}
}

19.1 Delivery Metadata

public record DeliveryMetadata(
        DestinationName destination,
        int deliveryAttempt,
        boolean redelivered,
        Optional<BrokerPosition> brokerPosition,
        Optional<String> partitionOrQueue,
        Optional<String> consumerGroup,
        Instant receivedAt) {
}

19.2 M1 규칙

  • handler는 broker ACK API를 호출하지 않는다.
  • Success 후에만 source settlement한다.
  • Retry는 destination policy가 전략을 결정한다.
  • DeadLetter는 DLQ publish confirm 후 source settlement한다.
  • Reject는 명시적으로 discard를 허용한 at-most-once profile에서만 허용한다.
  • handler timeout은 PROCESSING_TRANSIENT로 분류하되 side effect commit 여부가 불명확하면 duplicate 가능성을 기록한다.

20. M2 Manual Settlement

public interface ManualMessageHandler<T> {
    CompletionStage<Void> handle(
            MessageDelivery<T> delivery,
            SettlementController settlement);
}

public interface SettlementController {
    CompletionStage<SettlementResult> ack();
    CompletionStage<SettlementResult> retry(Duration delay);
    CompletionStage<SettlementResult> deadLetter(FailureDescriptor failure);
    CompletionStage<SettlementResult> reject(FailureDescriptor failure);
}

20.1 Batch Consume

public interface BatchMessageHandler<T> {
    CompletionStage<HandleResult> handle(BatchMessageDelivery<T> batch);
}

public record BatchMessageDelivery<T>(
        List<MessageDelivery<T>> deliveries,
        BatchDeliveryMetadata metadata) {
}

Batch consume은 M2다. batch 전체 settlement가 가능한 broker에서는 전체 결과를 사용하고, 개별 settlement가 가능한 broker에서는 item 결과를 유지한다. strict ordering destination에서는 하나의 ordering unit을 넘는 batch를 허용하지 않는다.

20.2 Pause·Resume와 Delayed Publish

public interface PauseResumeController {
    CompletionStage<Void> pause(DestinationName destination, String scope);
    CompletionStage<Void> resume(DestinationName destination, String scope);
}

public interface DelayedMessagePublisher {
    <T> CompletionStage<PublishResult> publish(
            MessageDestination<T> destination,
            MessageEnvelope<T> message,
            Instant deliverAt);
}

두 API 모두 M2이며 destination capability가 없으면 명시적으로 거부한다. Kafka 일반 topic은 native scheduled delivery capability를 선언하지 않는다. Rabbit delayed/quorum retry, Pulsar delayed delivery, NATS scheduler 계열은 adapter capability로 제공한다.

Manual API는 다음 guard를 강제한다.

  • 정확히 한 번만 terminal settlement 호출
  • handler 종료 전 settlement 누락 감지
  • source ACK 전 DLQ confirmation
  • application thread에서 broker channel·consumer object 접근 금지
  • settlement timeout과 SETTLEMENT_UNKNOWN 구분
  • duplicate call은 SettlementAlreadyCompletedException

21. Consumer 상태와 Settlement 증거

RECEIVED
  ↓
DECODING
  ├─→ SCHEMA_PARKING
  ↓
PROCESSING
  ├─→ RETRY_PENDING
  ├─→ DEAD_LETTER_PENDING
  ├─→ REJECTED
  ↓
HANDLER_SUCCEEDED
  ↓
SETTLEMENT_SENDING
  ├─→ SETTLED
  └─→ SETTLEMENT_UNKNOWN

Reliability module을 사용할 때만 다음 증거를 추가한다.

INBOX_RESERVED
BUSINESS_TRANSACTION_COMMITTED

21.1 Settlement 결과

public record SettlementResult(
        SettlementCompletion completion,
        SettlementEvidence evidence,
        Optional<FailureDescriptor> failure) {
}

public enum SettlementCompletion {
    SETTLED,
    REJECTED,
    UNKNOWN
}

UNKNOWN은 redelivery 가능성을 의미한다. 호출자는 이를 성공으로 간주하지 않는다.


22. 전달·처리·순서 보장 모델

public enum DeliveryGuarantee {
    AT_MOST_ONCE,
    AT_LEAST_ONCE
}

public enum ProcessingGuarantee {
    APPLICATION_IDEMPOTENT,
    BROKER_TRANSACTIONAL
}

public enum OrderingScope {
    NONE,
    DESTINATION,
    PARTITION,
    KEY
}

public enum ExternalSideEffectGuarantee {
    NONE,
    IDEMPOTENCY_REQUIRED,
    INBOX_TRANSACTIONAL
}

22.1 금지 모델

다음은 Core에 존재하지 않는다.

boolean exactlyOnce;
DeliveryGuarantee.EXACTLY_ONCE;
OrderingScope.GLOBAL;

22.2 실제 의미

모델 의미
AT_MOST_ONCE 유실 가능, 중복 억제 우선
AT_LEAST_ONCE redelivery 가능, 유실 방지 우선
APPLICATION_IDEMPOTENT handler가 message ID 또는 업무 key로 중복 effect를 막음
BROKER_TRANSACTIONAL broker가 정의한 transaction 범위에서만 원자화
INBOX_TRANSACTIONAL Inbox row와 DB side effect가 같은 transaction
PARTITION 한 partition 안의 broker order
KEY 동일 key의 mapping이 유지되는 동안의 순서

22.3 Ordering validator

다음 조합을 거부한다.

  • ordering=KEY + key resolver 없음
  • ordering=PARTITION + retry topic으로 reorder 허용 안 함
  • strict order + concurrency가 실제 ordering unit보다 큼
  • NATS ordered consumer + competing work queue 설정
  • Pulsar Shared + key ordering 요구
  • Kafka Share Group + ordered stream 요구

23. 오류 모델

MessagingException
 ├─ MessagingConfigurationException
 ├─ MessagingCapabilityUnavailableException
 ├─ MessageValidationException
 ├─ MessageSerializationException
 ├─ MessageSchemaIncompatibleException
 ├─ MessageTooLargeException
 ├─ MessageHeaderRejectedException
 ├─ MessagePublishRejectedException
 ├─ MessagePublishAmbiguousException
 ├─ MessagePublishTimeoutException
 ├─ MessageRoutingException
 ├─ MessageAuthenticationException
 ├─ MessageAuthorizationException
 ├─ MessageConsumerException
 ├─ MessageHandlerTimeoutException
 ├─ MessageSettlementException
 ├─ MessageSettlementUnknownException
 ├─ MessageRetryExhaustedException
 ├─ MessageDeadLetterException
 ├─ MessageRedriveException
 ├─ MessageTopologyException
 └─ MessageBrokerUnavailableException

23.1 Failure Category

public enum FailureCategory {
    TRANSIENT_INFRASTRUCTURE,
    THROTTLED,
    PROCESSING_TRANSIENT,
    PERMANENT_BUSINESS,
    POISON_MESSAGE,
    DESERIALIZATION,
    AUTHENTICATION,
    AUTHORIZATION,
    AMBIGUOUS,
    CONFIGURATION
}

23.2 Failure Descriptor

public record FailureDescriptor(
        FailureCategory category,
        String code,
        boolean retryable,
        String sanitizedMessage,
        Optional<String> exceptionType) {
}

Payload, full stack trace, credential, actual message key는 message header에 적재하지 않는다. Stack trace는 secure log storage에만 기록한다.


24. Retry Policy Engine

24.1 정책 모델

public record RetryPolicy(
        RetryMode mode,
        int maxAttempts,
        Duration initialDelay,
        Duration maxDelay,
        double multiplier,
        boolean jitter,
        OrderingImpact orderingImpact,
        Set<FailureCategory> retryableCategories,
        Set<FailureCategory> nonRetryableCategories) {
}

public enum RetryMode {
    NONE,
    INLINE,
    BLOCKING,
    PAUSE_PARTITION,
    RETRY_DESTINATION,
    BROKER_DELAYED
}

public enum OrderingImpact {
    PRESERVE,
    ALLOW_REORDER
}

24.2 결정 입력

public record RetryContext(
        DestinationProfile destination,
        DeliveryMetadata delivery,
        FailureDescriptor failure,
        MessagingCapabilities capabilities,
        boolean handlerMayHaveCommittedSideEffect) {
}

24.3 결정 결과

public sealed interface RetryDecision {
    record RetryInline(Duration delay) implements RetryDecision {}
    record PauseAndRetry(Duration delay) implements RetryDecision {}
    record PublishToRetryDestination(
            DestinationName destination,
            Duration delay) implements RetryDecision {}
    record DeadLetter(FailureDescriptor failure) implements RetryDecision {}
    record Reject(FailureDescriptor failure) implements RetryDecision {}
}

24.4 기본 규칙

  • DESERIALIZATION, AUTHENTICATION, AUTHORIZATION, CONFIGURATION은 자동 retry하지 않는다.
  • PERMANENT_BUSINESS는 DLQ 또는 reject 정책으로 보낸다.
  • TRANSIENT_INFRASTRUCTURE, THROTTLED, PROCESSING_TRANSIENT만 기본 retry 후보이다.
  • attempt는 최초 delivery를 1로 계산한다.
  • max attempts를 넘으면 DLQ 또는 parking으로 전환한다.
  • retry destination으로 이동해도 messageId를 유지한다.
  • retryAttempt, firstFailureAt, lastFailureAt은 reserved metadata에 기록한다.
  • source settlement는 retry destination publish confirmation 이후에만 수행한다.
  • strict ordering에서는 PAUSE_PARTITION 또는 blocking 전략만 허용한다.
  • Retry 중에도 consumer shutdown이 시작되면 신규 attempt를 생성하지 않는다.

25. DLQ·Parking·Redrive

25.1 DLQ 처리 순서

handler permanent failure 또는 retry exhausted
→ DeadLetterEnvelope 생성
→ DLQ publish
→ DLQ broker confirmation 확인
→ source settlement

DLQ publish가 실패하면 source를 ACK하지 않고 consumer를 pause하거나 원래 retry 정책으로 되돌린다.

25.2 Dead Letter Envelope

public record DeadLetterMetadata(
        MessageId originalMessageId,
        DestinationName originalDestination,
        Optional<BrokerPosition> originalPosition,
        Instant firstFailureAt,
        Instant lastFailureAt,
        int attempts,
        FailureCategory failureCategory,
        String failureCode,
        int redriveCount,
        Optional<RedriveId> lastRedriveId) {
}

원본 payload와 schema identity를 유지한다. 전체 stack trace는 header에 넣지 않는다.

25.3 Parking

역직렬화 불가, schema 미지원, 관리자 판정이 필요한 메시지는 일반 DLQ와 분리된 parking destination에 저장한다.

schema-parking
security-parking
manual-review-parking

25.4 Redrive

public record RedriveRequest(
        DestinationName sourceDlq,
        DestinationName target,
        Set<MessageId> messageIds,
        String operator,
        String reason,
        boolean dryRun) {
}

Redrive 규칙은 다음과 같다.

  • 기존 messageId 유지
  • redriveId 생성
  • redriveCount 증가
  • 대상 schema와 destination capability 재검증
  • dry-run 기본
  • 운영자·사유·선택 범위를 audit
  • 동일 redrive request의 idempotency 보장
  • redrive 성공 confirmation 후 DLQ 원본을 mark 또는 settlement

26. Kafka Stable Adapter

26.1 Stable 기준

  • Kafka 4.2 이상
  • 4.3.x release gate
  • Spring Kafka native integration
  • traditional consumer group Stable
  • Share Group은 별도 Experimental module

26.2 Producer 설정

Stable profile은 다음을 강제한다.

enable.idempotence=true
acks=all
retries > 0
max.in.flight.requests.per.connection <= 5

플랫폼 timeout과 Kafka delivery timeout의 관계를 startup에서 검증한다.

platform publish timeout
  >= request.timeout.ms
  <= delivery.timeout.ms + platform cleanup margin

ProducerRecord에는 logical messageId, schema metadata, trace context를 header로 기록한다.

26.3 Publish evidence

Kafka 결과 플랫폼 매핑
RecordMetadata 수신 CONFIRMED, BROKER_ACK 또는 profile 조건 충족 시 stronger evidence
serializer failure REJECTED, NOT_TRANSMITTED
authorization failure REJECTED
producer fenced REJECTED, non-retry
delivery timeout 전송 여부에 따라 AMBIGUOUS
connection loss after send AMBIGUOUS

Kafka acks=all을 영구 보존으로 표현하지 않는다. confirmation level은 현재 ISR acknowledgement 증거라는 adapter-specific detail을 diagnostic metadata에 남긴다.

26.4 Consumer Group

Stable consumer는 다음을 사용한다.

enable.auto.commit=false
isolation.level=read_committed  // transactional destination profile

M1의 strict order path는 partition별 한 개의 in-flight handler를 기본으로 한다.

poll
→ record를 partition work coordinator에 전달
→ partition pause
→ bounded worker에서 handler 실행
→ settlement command queue에 결과 기록
→ consumer poll thread가 command를 drain
→ contiguous offset commit
→ partition resume

이 구조는 handler thread가 Kafka consumer를 직접 호출하지 않게 한다.

26.5 Offset Tracker

public interface PartitionOffsetTracker {
    void delivered(TopicPartition partition, long offset);
    void completed(TopicPartition partition, long offset);
    OptionalLong highestContiguousCompleted(TopicPartition partition);
}

parallel handler를 허용해도 commit은 완료된 contiguous offset까지만 진행한다.

26.6 Retry

요구 전략
partition order 보존 pause partition + bounded retry
처리량 우선, reorder 허용 retry topic
짧은 transient failure inline retry
poison DLT

Retry topic 사용 시 원래 ordering을 보장하지 않는다고 profile과 metric에 표시한다.

26.7 Transaction Capability

Kafka transaction은 M3로 제공한다.

public interface KafkaTransactionalProcessor<K, V, R> {
    CompletionStage<R> process(
            KafkaTransactionalDelivery<K, V> delivery,
            KafkaTransactionalPublisher publisher);
}

보장 범위는 Kafka input offset과 Kafka output record다. 외부 DB·HTTP side effect는 포함하지 않는다.

26.8 Replay

Replay는 M2/Admin에서만 제공한다.

seek by offset
seek by timestamp
replay to isolated consumer group
replay to new destination

기존 production consumer group offset reset은 M4 위험 작업이다.

26.9 Tombstone

public interface KafkaTombstonePublisher {
    CompletionStage<PublishResult> publishTombstone(
            DestinationName destination,
            String key,
            MessageHeaders headers);
}

일반 MessageEnvelope의 null payload로 대체하지 않는다.


27. Kafka Share Group Experimental Adapter

Kafka 4.2+ Share Group은 record 단위 acknowledgement와 delivery attempt counting을 제공하지만 ordered stream abstraction으로 사용하지 않는다.

public interface KafkaShareWorkQueueCapability {
    <T> void register(
            MessageDestination<T> destination,
            MessageHandler<T> handler,
            ShareGroupOptions options);
}

제약은 다음과 같다.

  • OrderingScope.NONE만 허용
  • Work Queue destination에서만 사용
  • platform settlement mapping이 안정화되기 전 Experimental
  • traditional consumer group과 동일한 transaction API를 가정하지 않음
  • redelivery attempt와 share acknowledgement를 별도 native contract test로 검증

28. RabbitMQ Stable Adapter

28.1 Stable 기준

  • RabbitMQ 4.3.x
  • Spring AMQP native integration
  • durable work queue는 quorum queue 기본
  • publisher confirm과 consumer ACK 분리

28.2 Producer

Stable producer는 다음을 강제한다.

publisher-confirm-type=correlated
publisher-returns=true
mandatory=true

confirm과 return을 correlation ID로 결합한다.

public record RabbitPublishOutcome(
        RabbitConfirmOutcome confirm,
        RoutingOutcome routing,
        Optional<String> replyCode,
        Optional<String> replyText) {
}
상태 플랫폼 결과
confirm ACK + routed CONFIRMED
confirm ACK + returned REJECTED, UNROUTABLE
confirm NACK REJECTED
channel close before result AMBIGUOUS
confirm timeout AMBIGUOUS

28.3 Consumer

  • manual acknowledgement
  • prefetch로 in-flight 제한
  • handler success 후 ACK
  • transient failure는 requeue 또는 retry queue 정책
  • permanent failure는 confirmed DLQ publish 후 source ACK
  • channel·delivery tag를 application handler에 노출하지 않음

28.4 Queue 기본값

durable=true
auto-delete=false
exclusive=false
queue-type=quorum

classic queue는 명시적 low-durability profile에서만 허용한다.

28.5 Ordering

Rabbit queue는 enqueue FIFO를 출발점으로 하지만 다음 조건에서 처리 완료 순서는 달라질 수 있다.

consumer concurrency > 1
priority queue
NACK / requeue
redelivery
retry queue

따라서 Stable common contract는 OrderingScope.DESTINATION을 자동 선언하지 않는다. strict ordering profile은 single active consumer와 concurrency 1을 요구한다.

28.6 DLQ

두 모드를 제공한다.

  1. Platform-managed DLQ
    • target publish confirm 후 source ACK
    • broker-neutral 기본
  2. Native quorum at-least-once dead-lettering
    • M3 capability
    • 필요한 queue policy를 startup에서 검증

기본 dead-letter strategy가 at-most-once인 topology를 stronger guarantee로 표현하지 않는다.

28.7 Retry

inline retry
retry queue + TTL / dead-letter routing
RabbitMQ 4.3 quorum delayed retry capability

retry cycle은 x-death 또는 platform attempt metadata로 제한한다.

28.8 RequestReply

M2에서만 제공한다.

  • correlation ID 필수
  • reply timeout 필수
  • temporary reply queue lifecycle 관리
  • 장기 RPC 대체로 사용하지 않음
  • duplicate request·late reply 처리 정의

29. Reliability API

public interface ReliableMessagePublisher {
    <T> void addToOutbox(
            MessageDestination<T> destination,
            MessageEnvelope<T> message);
}

public interface IdempotentMessageHandler<T> {
    CompletionStage<HandleResult> handleOnce(
            String consumerName,
            MessageDelivery<T> delivery,
            TransactionalMessageAction<T> action);
}

Reliability module은 Core의 publish·delivery 계약을 바꾸지 않는다. DB transaction과 message identity를 결합한다.


30. Transactional Outbox

30.1 테이블

create table messaging_outbox (
    id uuid primary key,
    message_id uuid not null unique,
    destination varchar(160) not null,
    message_type varchar(240) not null,
    schema_version integer not null,
    content_type varchar(120) not null,
    aggregate_type varchar(160),
    aggregate_id varchar(320),
    headers jsonb not null,
    payload bytea not null,
    payload_hash varchar(128) not null,
    occurred_at timestamptz,
    created_at timestamptz not null,
    available_at timestamptz not null,
    status varchar(40) not null,
    attempts integer not null default 0,
    next_attempt_at timestamptz,
    lease_owner varchar(160),
    lease_until timestamptz,
    last_failure_category varchar(80),
    last_failure_code varchar(160),
    published_at timestamptz,
    version bigint not null default 0
);

create index ix_messaging_outbox_poll
    on messaging_outbox(status, available_at, next_attempt_at, created_at);

create index ix_messaging_outbox_lease
    on messaging_outbox(lease_until)
    where lease_owner is not null;

30.2 상태

PENDING
CLAIMED
PUBLISHED
RETRYABLE_FAILURE
PARKED

30.3 Relay 알고리즘

SELECT id FROM messaging_outbox
WHERE status IN ('PENDING', 'RETRYABLE_FAILURE')
  AND available_at <= now()
  AND coalesce(next_attempt_at, available_at) <= now()
ORDER BY created_at
FOR UPDATE SKIP LOCKED
LIMIT :batchSize
→ lease와 attempt 증가
→ DB transaction commit
→ Core publisher로 같은 messageId publish
→ CONFIRMED: PUBLISHED
→ REJECTED retryable: RETRYABLE_FAILURE
→ permanent: PARKED
→ AMBIGUOUS: RETRYABLE_FAILURE, 같은 messageId로 재시도

Outbox가 duplicate publish를 막는다고 광고하지 않는다. ambiguous retry는 같은 messageId를 사용하고 downstream Inbox가 duplicate side effect를 차단한다. PUBLISHED row는 감사·reconciliation 보존 기간 뒤 bounded cleanup job으로 삭제하거나 archive하며, PENDING, CLAIMED, RETRYABLE_FAILURE, PARKED row는 자동 삭제하지 않는다.

30.4 Debezium Profile

Debezium Outbox Event Router integration은 선택 recipe다.

  • outbox ID를 message ID로 사용
  • aggregate ID를 Kafka key로 사용 가능
  • CDC engine 자체 운영은 이 모듈 범위 밖
  • polling relay와 CDC relay를 동시에 활성화하지 않음

31. Inbox와 Idempotent Consumer

31.1 테이블

create table messaging_inbox (
    consumer_name varchar(200) not null,
    message_id uuid not null,
    message_type varchar(240) not null,
    payload_hash varchar(128) not null,
    received_at timestamptz not null,
    processed_at timestamptz not null,
    expires_at timestamptz not null,
    primary key (consumer_name, message_id)
);

create index ix_messaging_inbox_expiry
    on messaging_inbox(expires_at);

31.2 처리 알고리즘

DB transaction 시작
→ Inbox INSERT
→ unique conflict이면 duplicate로 판정하고 business mutation 생략
→ 신규 row이면 business mutation 실행
→ transaction commit
→ commit 후 source ACK

business mutation이 실패하면 Inbox INSERT도 rollback되어야 한다.

31.3 Retention

Inbox retention
  >= broker retention
   + 최대 DLQ 보관 기간
   + 최대 replay/redrive 기간
   + 안전 여유

retention을 짧게 잡아 오래된 replay가 side effect를 다시 만드는 구성을 startup warning 또는 validation failure로 처리한다.


32. Claim Check

1 MiB를 넘는 payload는 기본적으로 object storage 또는 fileserver에 저장하고 reference를 메시지로 전달한다.

public record ClaimCheckReference(
        String store,
        String objectId,
        long size,
        String checksumAlgorithm,
        String checksum,
        String contentType,
        Instant expiresAt) {
}

규칙은 다음과 같다.

  • public signed URL을 메시지에 직접 넣지 않는다.
  • consumer가 자신의 service credential로 조회한다.
  • size·checksum·content type을 검증한다.
  • object retention은 message retention·retry·DLQ·redrive 기간보다 길어야 한다.
  • object 삭제는 모든 consumer 처리 완료를 알 수 없으므로 명시적 lifecycle 정책을 사용한다.
  • Claim Check fetch 실패는 PROCESSING_TRANSIENT 또는 permanent integrity failure로 분류한다.

33. Topology Manifest와 Provisioning

33.1 Manifest

public record TopologyManifest(
        List<DestinationTopology> destinations,
        List<RetryTopology> retries,
        List<DeadLetterTopology> deadLetters,
        TopologyManagementMode managementMode) {
}

public enum TopologyManagementMode {
    AUTO_CREATE_DEV,
    VALIDATE_ONLY,
    ADMIN_MANAGED
}

33.2 환경 정책

환경 정책
local/test auto-create 허용
development auto-create 선택
staging IaC + validate-only
production IaC + validate-only, destructive mutation 금지

33.3 검증 항목

Kafka:

partition count
replication factor
min.insync.replicas
retention
cleanup policy
transaction capability

RabbitMQ:

exchange type/durability
queue type/durability
binding
quorum policy
dead-letter strategy
overflow
consumer timeout

Pulsar·NATS는 adapter capability에 따라 동일한 logical manifest에 native properties를 추가한다.


34. Admin Plane

public interface MessagingAdminService {
    TopologyValidationReport validateTopology();
    ReplayPlan planReplay(ReplayRequest request);
    ReplayResult executeReplay(ApprovedReplayPlan plan);
    RedrivePlan planRedrive(RedriveRequest request);
    RedriveResult executeRedrive(ApprovedRedrivePlan plan);
}

파괴 작업은 별도 interface에 둔다.

public interface DestructiveMessagingAdmin {
    DestructiveResult resetOffset(ApprovedOffsetReset request);
    DestructiveResult purge(ApprovedPurge request);
    DestructiveResult deleteDestination(ApprovedDelete request);
}

34.1 Guard

  • app credential로 bean 미생성
  • 별도 admin role
  • dry-run 기본
  • approval ID 필수
  • 대상·범위·예상 message 수 표시
  • 실행 전 topology version 재검증
  • audit event 저장
  • 동일 approval의 중복 실행 방지

35. Pulsar Experimental Adapter

지원 범위는 다음과 같다.

typed publish/consume
Exclusive / Failover / Shared / Key_Shared
broker acknowledgement
schema integration
redelivery
DLQ policy
replay cursor
multi-topic transaction capability

Stable 승격 조건:

  • 4.0 LTS와 4.2에서 Core Contract Suite 통과
  • Shared와 Key_Shared ordering 차이를 profile validator가 차단
  • transaction commit·abort·transactional ACK 장애 테스트 통과
  • native schema와 공통 Schema Registry 계약 정합성 확인
  • TLS·authentication·authorization production guard 통과

Pulsar 5 preview는 지원 기준으로 사용하지 않는다.


36. NATS JetStream Experimental Adapter

Core NATS pub/sub은 at-most-once이므로 reliability destination의 기본 transport로 사용하지 않는다.

JetStream 지원 범위:

stream publish + PubAck
Nats-Msg-Id deduplication
pull consumer
durable consumer
Explicit ACK
AckSync
Nak / NakWithDelay
AckWait / BackOff
MaxAckPending
MaxDeliver advisory
LimitsPolicy
WorkQueuePolicy
replay by sequence/time

NATS에는 Kafka·Pulsar형 transaction을 광고하지 않는다.

36.1 DLQ workflow

MaxDeliver 도달만으로 메시지가 자동 DLQ 이동됐다고 가정하지 않는다.

MaxDeliver advisory
→ parking workflow consumer
→ target publish + PubAck
→ 원본 terminal settlement 또는 admin mark

Stable 승격 조건:

  • dedupe window 경계 테스트
  • Ack()AckSync() 차이 검증
  • MaxDeliver advisory 기반 parking 테스트
  • stream failover와 redelivery contract 통과

37. Spring Cloud Stream Bridge

Spring Cloud Stream은 optional bridge다. Core contract를 대체하지 않는다.

public interface MessagingBindingBridge {
    void bindPublisher(DestinationName destination, String bindingName);
    void bindConsumer(DestinationName destination, String bindingName);
}

제약은 다음과 같다.

  • binder retry·DLQ 의미론을 Core 정책으로 자동 승격하지 않음
  • binder extended property는 M3 capability로 격리
  • Kafka transaction과 Rabbit routing 차이를 bridge가 숨기지 않음
  • Core PublishResult와 settlement evidence를 제공할 수 없는 binder 경로는 migration-only 등급

38. Security

38.1 Production 기본

TLS 필수
hostname/certificate validation 필수
broker 인증 필수
producer/consumer/admin identity 분리
destination-level 최소 권한
tenant namespace 분리
credential rotation 가능
secret header 금지
admin API 별도 credential

38.2 Credential Profile

public sealed interface BrokerCredentialProfile
        permits BrokerCredentialProfile.SaslScram,
                BrokerCredentialProfile.OAuth2,
                BrokerCredentialProfile.MutualTls,
                BrokerCredentialProfile.UsernamePassword,
                BrokerCredentialProfile.Nkey {
    String credentialId();
    record SaslScram(String credentialId) implements BrokerCredentialProfile {}
    record OAuth2(String credentialId) implements BrokerCredentialProfile {}
    record MutualTls(String credentialId) implements BrokerCredentialProfile {}
    record UsernamePassword(String credentialId) implements BrokerCredentialProfile {}
    record Nkey(String credentialId) implements BrokerCredentialProfile {}
}

credential은 secret provider에서 runtime에 가져온다. config 파일과 exception에 원문을 남기지 않는다.

38.3 ACL

  • Kafka topic/group/transactional ID 권한 분리
  • Rabbit vhost, exchange, queue configure/write/read 권한 분리
  • Pulsar tenant/namespace/topic 권한 분리
  • NATS account/subject publish·subscribe allowlist
  • Admin identity만 topology mutation 권한 보유

38.4 Message Security

  • payload-level encryption은 별도 codec profile
  • trace baggage allowlist
  • tenant context는 신뢰 경계에서 재검증
  • message header에 token·password·PII 원문 금지
  • schema parser에 크기·깊이 제한

39. Observability

OpenTelemetry messaging semantic convention은 exporter adapter로 사용하되 내부 public API에 convention attribute 이름을 고정하지 않는다.

39.1 Metric

영역 Metric
Producer publish count, confirmed, rejected, ambiguous, confirmation latency
Consumer received, processed, failed, processing latency
Settlement ack, retry, reject, dead-letter, unknown, latency
Reliability redelivery, retry, retry exhausted, DLQ, redrive
Backlog consumer lag, queue depth, pending ACK
Runtime connection, reconnect, rebalance, assignment
Schema encode/decode failure, incompatibility
Resource producer buffered bytes, in-flight, batch size, prefetch
Admin replay, redrive, offset reset, purge, delete
Outbox pending, leased, publish success, ambiguous, parked, age
Inbox inserted, duplicate, cleanup

39.2 Low-cardinality tag

허용:

broker
destinationProfile
destinationTemplate
operation
messageType  // bounded catalog일 때만
consumerGroupProfile
outcome
failureCategory
retryStage
schemaCodec

금지:

messageId
partitionKey
orderingKey
payload
actual dynamic destination
full exception message
customerId
unbounded tenantId
credential

39.3 Trace

messaging.publish logical span
  └─ broker publish attempt span

messaging.consume process span
  ├─ handler span
  ├─ retry publish span
  ├─ DLQ publish span
  └─ settlement span

producer와 consumer는 비동기 경계이므로 consumer process span은 message creation context와 span link를 사용한다.

39.4 Logging

INFO  destinationProfile=order-events outcome=CONFIRMED attempt=1
WARN  failureCategory=PROCESSING_TRANSIENT retryStage=RETRY_DESTINATION attempt=3
ERROR failureCategory=AMBIGUOUS destinationProfile=payment-events

payload와 full headers는 기본 로깅하지 않는다.


40. Backpressure·Payload·Concurrency

40.1 공통 제한

max payload bytes
max header bytes
max batch count
max batch bytes
max producer in-flight
max consumer concurrent handlers
max processing time
max retry concurrent publish
max DLQ backlog alarm

40.2 Broker mapping

공통 Kafka RabbitMQ Pulsar NATS
producer buffered bytes buffer.memory channel confirm window pending messages publish pending
max in-flight producer config confirm outstanding pending queue async publish pending
batch batch.size publisher batch batching publish batch
consumer concurrency partitions consumer count subscription consumers pull workers
consumer in-flight poll/partition tracker prefetch receiver queue MaxAckPending
backlog lag queue depth backlog stream pending

40.3 Overload 동작

  • producer buffer 포화 시 bounded wait 후 MessageBackpressureException
  • consumer handler queue 포화 시 partition/consumer pause
  • retry publish concurrency는 원본 consume concurrency와 분리
  • DLQ 장애가 지속되면 source consumer를 pause하고 alert
  • payload limit은 broker 전송 전에 local reject
  • batch bytes와 count 둘 다 제한

41. Lifecycle과 Graceful Shutdown

종료 순서는 다음과 같다.

신규 publish admission 차단
→ 신규 delivery handler 시작 차단
→ consumer pause
→ in-flight handler drain
→ 완료된 settlement 전송
→ producer confirm 대기
→ Outbox lease 반환
→ connection close

30초 기본 drain 이후 남은 작업은 다음처럼 처리한다.

  • unconfirmed publish: AMBIGUOUS 기록
  • unfinished handler: source 미settlement로 redelivery 허용
  • Outbox claimed row: lease 만료 후 재처리
  • manual settlement 미완료: warning + source redelivery

JVM shutdown hook만 신뢰하지 않고 Spring lifecycle phase를 사용한다.


42. Spring Boot Starter

42.1 Auto-configuration

MessagingCoreAutoConfiguration
MessagingSchemaAutoConfiguration
MessagingPolicyAutoConfiguration
KafkaMessagingAutoConfiguration
RabbitMessagingAutoConfiguration
MessagingReliabilityAutoConfiguration
MessagingAdminAutoConfiguration
MessagingObservabilityAutoConfiguration
MessagingSecurityAutoConfiguration

Experimental adapter는 별도 property와 classpath가 모두 있어야 활성화된다.

42.2 Configuration Properties

@ConfigurationProperties("backend.messaging")
public record MessagingProperties(
        Map<String, BrokerProperties> brokers,
        Map<String, DestinationProperties> destinations,
        MessagingLimitsProperties limits,
        MessagingSecurityProperties security,
        MessagingObservabilityProperties observability,
        MessagingAdminProperties admin) {
}

42.3 Actuator

/actuator/messaging
/actuator/messaging/topology
/actuator/messaging/outbox
/actuator/messaging/capabilities

Actuator는 payload, actual credential, 전체 dynamic topic name을 노출하지 않는다.


43. 테스트 전략

43.1 공통 Contract Suite

모든 adapter는 다음을 통과한다.

normal publish
publish reject
publish ambiguity
same messageId retry
normal consume
handler failure
settlement loss
redelivery
retry exhausted
DLQ confirmation
DLQ failure
redrive identity
ordering scope
payload limit
header policy
schema failure
credential failure
ACL failure
TLS failure
graceful shutdown
observability cardinality

43.2 Kafka Native Suite

acks=all
idempotent producer
leader failover
publish timeout ambiguity
rebalance
contiguous offset commit
pause/resume
retry topic reorder
read_committed
transaction commit/abort
producer fencing
seek/replay
Share Group acknowledgement experimental

43.3 Rabbit Native Suite

publisher confirm
mandatory return
unroutable
confirm timeout
channel close
manual ACK loss
requeue
prefetch
quorum minority loss
at-least-once DLX target outage
retry queue cycle
single active consumer order

43.4 Reliability Suite

DB commit 전 crash
DB commit 후 relay 전 crash
broker store 후 confirm loss
same messageId duplicate publish
consumer DB commit 후 ACK 전 crash
Inbox duplicate effect 차단
Outbox lease expiry
redrive duplicate request
Claim Check checksum mismatch

43.5 Pulsar·NATS Suite

Pulsar:

Shared / Key_Shared
transaction commit/abort
transactional ACK
schema compatibility
subscription redistribution

NATS:

PubAck
Nats-Msg-Id dedupe
Ack / AckSync
AckWait expiry
NakWithDelay
MaxDeliver advisory
stream failover

43.6 장애 도구

Testcontainers
Toxiproxy
process kill
broker node kill
network partition
latency / packet loss
PostgreSQL restart
credential rotation
TLS certificate rotation

44. 호환성 인증 매트릭스

대상 PR Nightly Release Gate
Kafka 4.2 Core subset Full 필수
Kafka 4.3.x Full Full + chaos 필수
RabbitMQ 4.3.x Full Full + chaos 필수
PostgreSQL 16 Outbox/Inbox Full crash suite 필수
Pulsar 4.0 LTS smoke Full Experimental 필수
Pulsar 4.2 smoke Full Experimental 필수
NATS 2.14.x smoke Full Experimental 필수
Spring Framework 6.2 line compile/test Full 필수
Spring Framework 7.0 line compile/test Full 필수
TLS·ACL subset rotation 필수
Performance 없음 baseline release candidate

45. 운영 설정 기본 정책

항목 기본
retry 비활성, destination별 명시
delivery at-least-once Stable 기본
auto settlement handler success 뒤
DLQ production at-least-once destination에 필수
topology production validate-only
TLS production 필수
payload max 1 MiB
Java serialization 차단
message type catalog 등록 필수
schema version 필수
producer confirm Stable destination 필수
consumer auto commit 차단
admin 기본 비활성
experimental 기본 비활성

46. 단계별 출시

Phase 1: Foundation Alpha

core API
envelope
schema JSON
capability
publish result
consumer settlement
policy validation
testkit

완료 조건: broker 없이 Core Contract와 architecture test 통과.

Phase 2: Kafka Stable Beta

producer
consumer group
offset tracker
pause/resume
retry
transaction capability
replay

완료 조건: leader failover, rebalance, ambiguity, transaction suite 통과.

Phase 3: Rabbit Stable Beta

confirm + return
quorum queue
manual ACK
prefetch
retry queue
DLQ

완료 조건: confirm loss, ACK loss, unroutable, quorum failover, DLQ failure 통과.

Phase 4: Reliability RC

Outbox
Inbox
Idempotent Consumer
Claim Check

완료 조건: crash matrix에서 duplicate DB effect가 발생하지 않음.

Phase 5: Operations Release

security
observability
topology validation
admin replay/redrive
starter
documentation

Phase 6: Experimental Compatibility

Kafka Share Group
Pulsar
NATS JetStream
Spring Cloud Stream bridge

47. 비지원 범위

초기 Stable release는 다음을 지원하지 않는다.

  • generic EXACTLY_ONCE=true
  • global ordering
  • raw broker client injection
  • application runtime의 purge·delete·offset reset
  • automatic XA
  • DB+broker atomicity 광고
  • automatic infinite retry
  • source ACK 후 DLQ publish
  • one-shot payload의 publish retry
  • arbitrary Java object serialization
  • unlimited headers·payload·batch
  • production auto topology mutation
  • Core NATS를 durable delivery로 사용
  • Pulsar preview release 지원
  • Kafka Share Group을 ordered stream으로 사용
  • Rabbit queue를 application completion FIFO로 광고
  • replay를 일반 handler API에서 수행
  • signed public URL을 Claim Check payload로 사용

48. 구현 결정 원장

질문 확정 답변
Core가 Spring Message<?>를 노출하는가? 아니오
Core async 타입은 무엇인가? CompletionStage
Reactive는 어디서 제공하는가? facade/adapter module
기본 브로커는 무엇인가? 기본 하나를 정하지 않고 destination profile이 선택
Stable adapter는? Kafka, RabbitMQ
Experimental adapter는? Kafka Share Group, Pulsar, NATS
publish timeout은 성공·실패를 확정하는가? 아니오, AMBIGUOUS 가능
messageId는 retry에서 바뀌는가? 아니오
DLQ 이동 후 source ACK 순서는? DLQ confirm 후 ACK
retry topic은 ordering을 보존하는가? 아니오
Outbox만으로 exactly-once인가? 아니오
Inbox transaction 범위는? Inbox row + business DB effect
topology는 누가 생성하는가? 운영 IaC, dev/test만 auto-create
large payload는? Claim Check
Raw bytes는? M2
Java serialization은? 비지원
Admin credential은 app과 공유하는가? 아니오
Experimental 기능은 기본 활성화되는가? 아니오

49. 완료 정의

플랫폼은 다음 조건을 모두 만족할 때 Stable로 간주한다.

공개 계약

  • M1만으로 typed publish와 consume을 구현할 수 있다.
  • PublishResult가 evidence와 ambiguity를 표현한다.
  • handler success 이전 settlement가 구조적으로 불가능하다.
  • broker SDK 타입이 Core에 노출되지 않는다.

Kafka

  • idempotent producer와 acks=all guard가 동작한다.
  • leader failover에서 confirmed, rejected, ambiguous를 구분한다.
  • rebalance와 async handler에서 contiguous offset만 commit한다.
  • transaction capability의 범위를 Kafka 내부로 제한한다.

RabbitMQ

  • confirm과 routing outcome을 결합한다.
  • unroutable message를 confirmed success로 처리하지 않는다.
  • ACK 유실과 publish confirm 유실을 별도로 검증한다.
  • quorum queue와 DLQ 조건을 startup에서 검증한다.

Reliability

  • Outbox relay crash 후 같은 message ID로 재발행한다.
  • Inbox가 duplicate DB effect를 차단한다.
  • DLQ·redrive에서 identity와 schema를 유지한다.
  • Claim Check가 size·checksum·retention을 검증한다.

운영

  • topology drift를 startup 또는 admin report에서 발견한다.
  • app credential로 destructive operation이 불가능하다.
  • TLS·ACL·credential rotation suite가 통과한다.
  • metric cardinality와 secret leak 검사가 통과한다.
  • payload·in-flight·backlog 상한 안에서 부하 테스트가 통과한다.

문서

  • support matrix
  • configuration reference
  • delivery guarantees
  • retry and DLQ guide
  • outbox and inbox guide
  • broker operations runbook
  • migration guide
  • experimental feature policy

이 문서의 설계 결정은 구현 계획의 Global Constraints와 각 Task의 acceptance test에 그대로 반영한다.