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개가 남음.
6.7 KiB
Messaging 지원 매트릭스
플랫폼이 무엇을 보장하는지와 무엇을 보장하지 않는지를 브로커별로 고정한다. 여기 없는 조합은 지원되지 않는다.
브로커 등급
| 브로커 | 등급 | 인증 기준 | Stable 기능 | 제한 |
|---|---|---|---|---|
| Kafka | Stable | 4.2+ / 4.3.x | producer idempotence, consumer group, batch, pause/resume, replay, transaction capability | Share Group은 Experimental |
| RabbitMQ | Stable | 4.3.x | exchange/routing, publisher confirm, mandatory return, manual ACK, quorum queue, retry queue, DLQ | stream 및 특수 plugin 미지원 |
| Pulsar | Experimental | 4.0 LTS + 4.2 | typed publish/consume, Shared, Key_Shared, schema | transaction 미승격, 기본 비활성 |
| NATS JetStream | Experimental | 2.14.x | stream, durable consumer, explicit ACK, dedupe, replay | native DLQ 없음(플랫폼이 대행), 기본 비활성 |
| Artemis/JMS | Extension | 범위 밖 | adapter SPI만 | 별도 ADR + Contract Suite 통과 필요 |
Capability 매트릭스
MessagingCapabilities가 런타임에 선언하는 값이다. false인 기능을 요구하는 destination profile은
startup에서 실패하며, 조용히 약화되지 않는다.
| Capability | Kafka | Kafka Share | RabbitMQ | Pulsar | NATS JS |
|---|---|---|---|---|---|
| brokerAcknowledgement | O | O | O | O | O |
| replicationOrPersistenceEvidence | O | O | O | O | O |
| perMessageSettlement | O | O | O | O | O |
| batchSettlement | O | X | X | O | O |
| orderedStream | O | X | X | X | O |
| keyedOrdering | O | X | X | Key_Shared만 | X |
| replay | O | X | X | O | O |
| delayedDelivery | X | X | retry queue로 대행 | O | X |
| brokerTransaction | O | X | X | 미승격 | X |
| deduplicatedPublish | O | X | X | X | O |
| nativeDeadLetter | X | X | O | O | X |
| topologyManagement | O | X | O | O | O |
Kafka Share Group이 ordering 전부 X인 것은 설계 결정이다. share group은 개별 record를
경쟁 소비자에게 나눠주고 개별 ack하므로 partition 순서를 유지할 수 없다. ordered destination을
share group에 설정하면 KafkaShareProfileValidator가 거부한다.
NATS JetStream의 nativeDeadLetter=X도 마찬가지다. JetStream은 delivery limit 초과 시 메시지를
terminate할 뿐 어디로도 라우팅하지 않으므로, 플랫폼이 DLQ publish를 직접 수행한다.
기능 등급
| 기능 | 등급 |
|---|---|
| Typed Publish·Consume | Stable M1 |
| At-least-once contract | Stable |
| Ambiguous publish 결과 | Stable |
| handler 성공 후 자동 settlement | Stable M1 |
| Batch / Manual settlement / Pause·Resume / Delayed / Replay 요청 | M2 |
| Broker transaction / partition / routing / subscription | M3 |
| Replay 실행 / Redrive / offset reset / purge / delete | M4 Admin |
| Kafka Share Group, Pulsar, NATS | Experimental |
| Spring Cloud Stream bridge | Optional |
무엇이 "Stable"을 증명하는가
Stable 등급은 두 가지를 모두 통과해야 한다. CompatibilityMatrixTest가 이 규칙을 강제한다.
1. 공유 Contract Suite (MessagingAdapterContract, 7개)
Kafka와 RabbitMQ가 동일한 7개 테스트를 변경 없이 통과한다. 결정적 하네스를 쓰므로 확인 유실·settlement 유실 같은 장애를 요청 시점에 재현할 수 있다.
2. 실 브로커 인증 (Testcontainers)
| 스위트 | 무엇을 증명하는가 |
|---|---|
KafkaBrokerIT |
acks=all이 실제 replication 증거를 만든다 / 잘못된 토픽은 REJECTED / 발행-소비 왕복에서 identity 보존 및 contiguous commit |
KafkaAmbiguityChaosIT |
브로커를 docker pause로 멈춘 상태의 publish가 AMBIGUOUS 로 보고된다 (broker acceptance 없음, confirmation level NONE, 비-retryable) |
RabbitBrokerIT |
exchange가 confirm했는데 어떤 큐에도 바인딩되지 않은 publish가 REJECTED + UNROUTABLE 로 보고된다 |
OutboxPostgresIT |
롤백된 트랜잭션은 발행 가능한 행을 남기지 않는다 / SKIP LOCKED lease가 두 relay를 분리한다 / ambiguous 행이 같은 messageId로 재클레임된다 |
InboxPostgresIT |
재전달이 side effect를 두 번 적용하지 않는다 / 롤백은 예약도 되돌린다 |
Docker가 없으면 DockerAvailability 가드로 skip되며, 이 표의 항목은 그때 검증되지 않은 것으로 취급한다.
3. 장애 시나리오 커버리지 (BrokerFailureMatrix)
NetworkFaultScenario가 5개 시나리오와 각각의 기대 결과를 코드로 고정한다. 기대 결과를 어댑터별로
두지 않는 것이 핵심이다 — 어댑터마다 다른 답을 허용하면 공유 계약이 존재할 이유가 없다.
| 시나리오 | 시점 | 기대 결과 | 이유 |
|---|---|---|---|
connection-refused |
전송 전 | REJECTED |
바이트가 나가지 않았으므로 broker가 가질 수 없다 |
connection-cut-after-write |
전송 후 | AMBIGUOUS |
broker가 저장했고 confirm만 유실됐을 수 있다 |
confirm-timeout |
전송 후 | AMBIGUOUS |
timeout은 부재의 증거가 아니라 증거의 부재다 |
settlement-lost |
settlement 중 | REDELIVERED |
미settlement 메시지는 재전달이 설계다 |
high-latency |
전송 후 | AMBIGUOUS |
판단 시점에는 confirm 유실과 구별할 수 없다 |
CrossBrokerContractSuite가 릴리스 게이트로 이를 강제한다. Stable 어댑터는 5개 전부를 실 브로커에서
커버해야 하고, Experimental 어댑터는 LIVE_BROKER 커버리지를 주장할 수 없다. 커버리지는 능력이 아니라
무엇을 실제로 돌렸는지의 기록이다.
실 브로커가 실제로 잡아낸 결함
이 스위트들은 장식이 아니다. 작성 과정에서 결정적 테스트가 통과하는데 실 인프라에서 실패한 결함을 두 건 잡았다.
- Outbox
IN_FLIGHT고아 행 — lease 쿼리가PENDING/AMBIGUOUS만 클레임 대상으로 봐서, publish 도중 죽은 relay가 남긴 행이 lease 만료 후에도 영영 회수되지 않았다. - Rabbit confirm 경합 — transport가 publish 후에 confirm을 등록해서, 연결 스레드에서 confirm이 먼저 도착하면 유실되고 호출자가 무한 대기했다.
둘 다 인메모리 double이 실제보다 관대해서 통과하고 있었다.
명시적 비지원
- 공통
EXACTLY_ONCE설정 —DeliveryGuarantee에 상수가 존재하지 않는다. - 전역 순서 —
OrderingScope에GLOBAL이 존재하지 않는다. - DB와 broker의 자동 원자 transaction, 기본 XA
- Java native serialization
- 무제한 payload·header, 무한 retry
- 운영 application에서의 topology 파괴 작업
- 일반 애플리케이션에 raw broker client 반환
- DLQ publish 확인 전 source ACK