refactor: 빌드 로직 개선, gradle 파일 경량화
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
# Outbox Transport-Only Cutover Design
|
||||
|
||||
## Status
|
||||
|
||||
Approved implementation slice for MSG-015 transport-only cutover.
|
||||
|
||||
This design deliberately does **not** activate `POLLING_V2` and does not migrate the publication authority to the v2 delivery tables. The existing `outbox_event` writer/store/claim/status authority remains the only active authority. The change makes that legacy authority capable of carrying a canonical integration event without losing the exact platform envelope.
|
||||
|
||||
## Goal
|
||||
|
||||
Support both row generations under one legacy relay authority:
|
||||
|
||||
```text
|
||||
business transaction
|
||||
-> legacy NewOutboxEvent -> legacy row
|
||||
-> canonical ValidatedIntegrationEvent -> canonical-compatible row
|
||||
|
||||
one OutboxStorePort claim authority
|
||||
-> legacy claimed row -> MessageBroker compatibility path
|
||||
-> canonical claimed row -> IntegrationEventPublishPort -> messaging platform
|
||||
```
|
||||
|
||||
A row is published through exactly one branch. There is no dual write and no second relay scheduler.
|
||||
|
||||
## Application boundaries
|
||||
|
||||
### Canonical append
|
||||
|
||||
`OutboxAppendPort` becomes the canonical durable append boundary:
|
||||
|
||||
```java
|
||||
void append(ValidatedIntegrationEvent event);
|
||||
```
|
||||
|
||||
### Legacy append
|
||||
|
||||
Raw R0 payload append moves to an explicitly named compatibility port:
|
||||
|
||||
```java
|
||||
LegacyOutboxAppendPort
|
||||
void append(NewOutboxEvent event);
|
||||
```
|
||||
|
||||
Existing sample/durable-operation code that still emits raw `NewOutboxEvent` uses only the legacy port. New canonical code must not call the legacy port.
|
||||
|
||||
### Claimed row model
|
||||
|
||||
The relay-facing row is a sealed application model:
|
||||
|
||||
```text
|
||||
ClaimedOutboxEvent
|
||||
|- OutboxEvent // legacy R0 claim model retained for compatibility
|
||||
`- CanonicalClaimedOutboxEvent // reconstructs one ValidatedIntegrationEvent
|
||||
```
|
||||
|
||||
`OutboxStorePort.claimBatch` returns `List<ClaimedOutboxEvent>`.
|
||||
|
||||
Common relay state is exposed by the sealed interface: event id, event type, aggregate id, occurred-at, status and attempt count. The canonical subtype also exposes the exact `ValidatedIntegrationEvent`.
|
||||
|
||||
A persisted row with a **partial** canonical metadata set is corrupt and fails closed during mapping. It is never downgraded to the legacy path.
|
||||
|
||||
## Storage compatibility projection
|
||||
|
||||
The existing PostgreSQL `outbox_event` remains authoritative. Add a forward migration after current legacy V12 that:
|
||||
|
||||
- widens `event_id` to `varchar(96)`;
|
||||
- widens `correlation_id` to `varchar(128)`;
|
||||
- adds nullable canonical columns to preserve existing rows;
|
||||
- adds an all-or-none canonical-shape check;
|
||||
- stores exact canonical envelope bytes in `bytea`;
|
||||
- keeps the legacy required columns for the rollback window.
|
||||
|
||||
Canonical required columns:
|
||||
|
||||
```text
|
||||
contract_id
|
||||
envelope_version
|
||||
payload_version
|
||||
logical_destination
|
||||
tenant_scope
|
||||
aggregate_type
|
||||
aggregate_sequence
|
||||
event_index
|
||||
partition_key
|
||||
envelope_bytes
|
||||
content_type
|
||||
schema_set_hash
|
||||
envelope_sha256
|
||||
envelope_schema_hash
|
||||
payload_schema_hash
|
||||
contract_catalog_revision
|
||||
destination_binding_revision
|
||||
```
|
||||
|
||||
`causation_id` is optional by the application contract.
|
||||
|
||||
Existing legacy columns remain populated for canonical rows with this compatibility projection:
|
||||
|
||||
```text
|
||||
event_id = canonical event id
|
||||
aggregate_id = canonical aggregate id
|
||||
event_type = contract id
|
||||
payload = exact envelope bytes decoded as strict UTF-8
|
||||
occurred_at = canonical occurred-at
|
||||
status = PENDING
|
||||
attempt_count = 0
|
||||
next_attempt_at = occurred-at
|
||||
correlation_id = canonical correlation id
|
||||
idempotency_key = event id
|
||||
```
|
||||
|
||||
The canonical encoder currently emits a UTF-8 JSON envelope. The append adapter verifies strict UTF-8 round-trip before storing the compatibility text. Invalid UTF-8 fails the business transaction; replacement characters are forbidden.
|
||||
|
||||
`partitionKeyBytes` is not stored separately because the canonical model already requires it to be exactly the US-ASCII bytes of `partitionKeyText`. The claimed model reconstructs those bytes from the stored canonical text.
|
||||
|
||||
## Persistence adapters
|
||||
|
||||
`OutboxStoreAdapter` remains the legacy claim/status store and implements `LegacyOutboxAppendPort`, not `OutboxAppendPort`.
|
||||
|
||||
A separate `CanonicalOutboxAppendAdapter` implements `OutboxAppendPort`. It participates in the caller's existing write transaction exactly like the legacy adapter and never opens a local transaction.
|
||||
|
||||
Both write the same `outbox_event` table; they are alternative semantic inputs, not dual writers for one business fact.
|
||||
|
||||
## Activation
|
||||
|
||||
Introduce:
|
||||
|
||||
```text
|
||||
ca-skeleton.outbox.canonical-transport-enabled=false
|
||||
```
|
||||
|
||||
Default remains false.
|
||||
|
||||
When false:
|
||||
- existing legacy append/relay behavior is unchanged;
|
||||
- canonical append bean is not exposed;
|
||||
- canonical relay routing is not considered an active deployment capability.
|
||||
|
||||
When true:
|
||||
- canonical append bean is exposed;
|
||||
- startup requires an `IntegrationEventPublishPort`;
|
||||
- the relay publisher can route canonical claimed rows to that port;
|
||||
- legacy rows continue through `MessageBroker`;
|
||||
- while mixed legacy rows may still exist, a relay-enabled deployment still requires the legacy broker. Canonical transport is an additional route, not permission to strand legacy backlog.
|
||||
|
||||
The gate is a compatibility/cutover gate only. It does not change DB publication authority and does not activate `POLLING_V2`.
|
||||
|
||||
## Publish routing
|
||||
|
||||
`OutboxMessagePublishPort` remains the one relay publish port and accepts `ClaimedOutboxEvent`.
|
||||
|
||||
Implementation behavior:
|
||||
- `OutboxEvent` -> existing `OutboxEnvelopeJson` + `MessageBroker`.
|
||||
- `CanonicalClaimedOutboxEvent` -> exact stored `ValidatedIntegrationEvent` -> `IntegrationEventPublishPort`.
|
||||
|
||||
The canonical branch blocks on the returned `CompletionStage` only at this legacy compatibility boundary, because the current legacy relay port is synchronous. The platform result is mapped unchanged into `OutboxPublishOutcome`.
|
||||
|
||||
The bridge does not re-encode canonical bytes.
|
||||
|
||||
If canonical transport is disabled or the canonical publisher is absent, canonical publication fails closed before broker/platform transmission. Startup validation prevents the normal configured case from reaching that state.
|
||||
|
||||
## Outcome policy
|
||||
|
||||
The existing legacy relay state machine remains authoritative in this slice:
|
||||
- CONFIRMED -> mark PUBLISHED.
|
||||
- AMBIGUOUS -> retryable legacy FAILED flow.
|
||||
- REJECTED_BEFORE_SEND / REJECTED_AFTER_BROKER -> existing definite-refusal DEAD behavior.
|
||||
|
||||
This is intentionally the existing compatibility semantics. The richer v2 per-attempt state machine is a later storage-authority cutover.
|
||||
|
||||
## Non-goals
|
||||
|
||||
This slice does not:
|
||||
- switch `OutboxPublicationAuthority` to `POLLING_V2`;
|
||||
- mutate/reconcile `outbox_event_log_v2` or `outbox_delivery_v2`;
|
||||
- implement CDC;
|
||||
- remove `MessageBroker`, `KafkaSender`, `NewOutboxEvent`, `OutboxEvent`, or the legacy scheduler;
|
||||
- migrate old rows into canonical rows;
|
||||
- invent tenant, trace, schema or routing metadata for old rows.
|
||||
|
||||
## Verification
|
||||
|
||||
Required:
|
||||
1. application port split compiles and old raw producers use `LegacyOutboxAppendPort`;
|
||||
2. migration integration proves additive columns, exact BYTEA, constraints and legacy compatibility;
|
||||
3. canonical append adapter round-trips every canonical field and exact bytes;
|
||||
4. partial canonical row mapping fails closed;
|
||||
5. legacy row mapping remains unchanged;
|
||||
6. relay unit test proves canonical row invokes only `IntegrationEventPublishPort`;
|
||||
7. legacy row invokes only `MessageBroker`;
|
||||
8. canonical bytes reaching `PlatformIntegrationEventPublishAdapter` are byte-identical;
|
||||
9. startup rejects canonical transport enabled without `IntegrationEventPublishPort`;
|
||||
10. default-off composition preserves current behavior;
|
||||
11. architecture/dependency checks and `git diff --check` pass.
|
||||
Reference in New Issue
Block a user