4.8 KiB
Messaging Platform Bridge Design
Goal
Replace the application-specific broker seam with one canonical anti-corruption bridge:
application-core IntegrationEventPublishPort
-> adapter/outbound/messaging/platformbridge
-> messaging-schema-api EncodedMessagePublisher
-> messaging-runtime-core DefaultMessagePublisher
-> messaging transport/runtime
The bridge must preserve canonical event identity and exact encoded bytes while reusing the platform's destination resolution, authorization, admission, runtime leasing, transport normalization, and observation pipeline.
Scope
This phase introduces and verifies the canonical bridge. It does not migrate the legacy outbox storage/relay rows, because OutboxEvent does not retain the schema/order/tenant metadata required to reconstruct ValidatedIntegrationEvent without invention.
Application boundary
Create IntegrationEventPublishPort in application-core.
Signature:
CompletionStage<OutboxPublishOutcome> publish(ValidatedIntegrationEvent event);
The application package depends only on its own canonical event model and application outcome vocabulary.
Adapter bridge
PlatformIntegrationEventPublishAdapter lives under:
adapter/outbound/messaging/platformbridge
It depends on EncodedMessagePublisher, never on a concrete broker client, runtime-core implementation, or transport SPI.
Mapping rules:
logicalDestinationId-> platformDestinationName.contractId-> platformMessageType.payloadVersion->SchemaVersion.- event and causation identities must parse as UUIDv7; values are preserved exactly. Incompatible identities fail closed before the platform publisher is called.
occurredAtis used for bothproducedAtandoccurredAtuntil the application canonical model carries a separate production timestamp. The bridge never invents a new timestamp.- producer is an explicit constructor/configuration value.
- correlation, partition key, tenant, aggregate order and exact envelope bytes are preserved.
- trace context is explicitly absent (
TraceContext.none()) until the application model owns canonical trace context. - exact
envelopeBytesbecomeEncodedMessagebytes; no re-encoding occurs. - schema/catalog/binding/envelope evidence that has no first-class platform field is preserved as bounded
x-ca-*headers. - the schema reference subject is the canonical contract id and version is the canonical payload version.
Outcome mapping
Mapping is based on completion and transmission evidence, not enum name similarity:
- CONFIRMED ->
OutboxPublishOutcome.CONFIRMED. - AMBIGUOUS ->
OutboxPublishOutcome.AMBIGUOUS. - REJECTED + NOT_TRANSMITTED ->
REJECTED_BEFORE_SEND. - REJECTED + any evidence that bytes may have left the process ->
REJECTED_AFTER_BROKER.
Bridge preparation failures are definite pre-send rejection.
Platform boundary
EncodedMessagePublisher is owned by messaging-schema-api, because EncodedMessage is owned there and the dependency direction remains acyclic.
DefaultMessagePublisher implements both MessagePublisher and EncodedMessagePublisher. The encoded path skips only codec lookup/encoding; destination resolution, access policy, admission, runtime lease, transport send, deadline handling, result normalization and observation are shared with the normal publish path.
The starter exposes one DefaultMessagePublisher singleton, which therefore satisfies both public interfaces.
Spring ownership
MessagingBridgeRootAutoConfiguration owns the bridge bean when an EncodedMessagePublisher is present and app.messaging.producer-id is explicitly configured. Producer identity is never inferred from spring.application.name or invented. Application bootstrap must not construct Kafka producer clients or implement broker-specific send behavior.
The existing KafkaSender / KafkaMessageBroker path remains temporarily for the legacy OutboxEvent and realtime publishers, which do not yet carry enough canonical metadata to enter the new bridge without invention. It is explicitly transitional and is removed only with the legacy outbox/realtime cutover. The new canonical bridge never calls it.
Verification
Required checks:
DefaultMessagePublisherTest: pre-encoded publish preserves bytes and skips codec while still exercising central pipeline.PlatformIntegrationEventPublishAdapterTest: golden mapping, outcome mapping, fail-closed identity behavior.- outbound messaging module tests/check.
- messaging runtime/starter tests.
- app-bootstrap system test and architecture test after adding the canonical bridge while retaining the documented legacy seam.
- search proving app-bootstrap has no direct native Kafka sender configuration.
- dependency/build lock refresh only where dependency ownership changed.
git diff --check.