1.6 KiB
1.6 KiB
Source module → Operations Projection example
A source module does not call an arbitrary Exception Center REST endpoint from its domain transaction. Prefer an Outbox event and an idempotent consumer that updates the operational projection.
Example flow:
OMS AllocationFailed
↓ same OMS transaction
Outbox
↓
Operations projection consumer
↓
OperationsProjectionWriter.UpsertAsync
↓
COMMON-OPS-001
When the source condition becomes normal:
OMS AllocationSucceeded
↓
Outbox
↓
OperationsProjectionWriter.ResolveBySourceAsync
Suggested mapping:
await writer.UpsertAsync(new UpsertWorkItem(
tenantId,
"OMS",
"order",
orderId.ToString(),
orderNo,
"OMS-ORD-001",
orderVersion,
"STOCK_SHORTAGE",
$"재고 부족 · {orderNo}",
"출고 가능한 재고가 부족합니다.",
"warning",
occurredAt,
dueAt: shipmentCutoff,
context: new { orderNo, shortageSkuCount }), ct);
Recovery:
await writer.ResolveBySourceAsync(
tenantId,
"OMS",
"order",
orderId.ToString(),
"STOCK_SHORTAGE",
"재고 할당 성공",
orderVersion,
ct);
This keeps the source module authoritative and makes the exception center disposable/rebuildable as a projection.
Out-of-order event protection
source_version is part of the projection contract. An older or duplicate failure event must not reopen an exception after a newer recovery event has resolved it. Producers should therefore publish a monotonically increasing aggregate/source version whenever possible.