spakky-outbox¶
spakky-outbox는 Integration Event를 비즈니스 데이터와 같은 transaction에 원자적으로 기록하고 별도 Relay로 전송하기 위한 계약을 제공합니다. 브로커가 수락 가능한 레코드는 확인될 때까지 재전송되지만, 영구적인 레코드 귀속 거부는 retry 소진 후 성공 전달 없이 abandoned 처리될 수 있습니다.
Outbox 패턴 — 원자적 기록과 Relay 상태 계약
플러그인 진입점¶
Plugin initialization entry point.
initialize(app)
¶
Initialize the Outbox plugin.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
SpakkyApplication
|
The Spakky application instance. |
required |
Source code in core/spakky-outbox/src/spakky/outbox/main.py
EventBus¶
Outbox Event Bus — sync and async implementations replacing IEventBus/IAsyncEventBus via @Primary.
OutboxEventBus(storage, propagator, auth_snapshot_headers=None)
¶
Bases: IEventBus
Intercepts integration events and stores them in the Outbox table (sync).
Replaces the default DirectEventBus so that events are persisted atomically within the same database transaction as the business data.
Source code in core/spakky-outbox/src/spakky/outbox/bus/outbox_event_bus.py
AsyncOutboxEventBus(storage, propagator, auth_snapshot_headers=None)
¶
Bases: IAsyncEventBus
Intercepts integration events and stores them in the Outbox table (async).
Replaces the default AsyncDirectEventBus so that events are persisted atomically within the same database transaction as the business data.
Source code in core/spakky-outbox/src/spakky/outbox/bus/outbox_event_bus.py
포트¶
Outbox storage port.
IOutboxStorage
¶
Bases: ABC
Synchronous outbox message storage abstraction.
save(message)
abstractmethod
¶
fetch_pending(limit, max_retry)
abstractmethod
¶
Claim unpublished messages for this relay instance (with lock).
A partition key must be claimed whole: an implementation may only hand out messages of a key when it also claims that key's oldest message that is neither published nor abandoned. Otherwise two relay instances publish one key in parallel and lose the ordering the key exists to provide. Messages without a partition key carry no such constraint.
Source code in core/spakky-outbox/src/spakky/outbox/ports/storage.py
mark_published(message_id)
abstractmethod
¶
increment_retry(message_id)
abstractmethod
¶
mark_abandoned(message_id)
abstractmethod
¶
Record that the relay gave up on a message after exhausting retries.
The message leaves the pending queue without being published, so a partition key never waits forever on a message that will not be retried again. An implementation must keep the record and the reason readable — the operator has to be able to find what was dropped.
Source code in core/spakky-outbox/src/spakky/outbox/ports/storage.py
IAsyncOutboxStorage
¶
Bases: ABC
Asynchronous outbox message storage abstraction.
save(message)
abstractmethod
async
¶
fetch_pending(limit, max_retry)
abstractmethod
async
¶
Claim unpublished messages for this relay instance (with lock).
A partition key must be claimed whole: an implementation may only hand out messages of a key when it also claims that key's oldest message that is neither published nor abandoned. Otherwise two relay instances publish one key in parallel and lose the ordering the key exists to provide. Messages without a partition key carry no such constraint.
Source code in core/spakky-outbox/src/spakky/outbox/ports/storage.py
mark_published(message_id)
abstractmethod
async
¶
increment_retry(message_id)
abstractmethod
async
¶
mark_abandoned(message_id)
abstractmethod
async
¶
Record that the relay gave up on a message after exhausting retries.
The message leaves the pending queue without being published, so a partition key never waits forever on a message that will not be retried again. An implementation must keep the record and the reason readable — the operator has to be able to find what was dropped.
Source code in core/spakky-outbox/src/spakky/outbox/ports/storage.py
Relay¶
Outbox Relay Background Services (sync and async).
OutboxRelayBackgroundService(storage, transport, config)
¶
Bases: AbstractBackgroundService
Polls the Outbox storage and relays pending messages to the transport (sync).
Initialize with storage, transport, and config dependencies.
Source code in core/spakky-outbox/src/spakky/outbox/relay/relay.py
initialize()
¶
dispose()
¶
run()
¶
Poll the outbox storage and relay pending messages until stopped.
Source code in core/spakky-outbox/src/spakky/outbox/relay/relay.py
__register_refusal(message, halted_partition_keys)
¶
Spend one retry on a refused message, or abandon it when none is left.
A message whose budget is spent will never be fetched again, so it is abandoned rather than left behind: that releases its partition key, which would otherwise wait forever on a message nobody will retry, and keeps the record findable. A message that still has budget holds its key back until it is delivered, so nothing of that key overtakes it.
Source code in core/spakky-outbox/src/spakky/outbox/relay/relay.py
__publish_one_at_a_time(messages, halted_partition_keys)
¶
Replay a batch whose flush failed, confirming one message at a time.
A batch flush reports that the broker refused something but not what, so neither the retry budget nor the partition key hold-back can be aimed without replaying. Confirming one message per flush puts the broker's verdict on the message that earned it: the refused message spends its budget and holds back the rest of its key, while its neighbours publish. Only a refusal counts — a transport that cannot reach the broker at all stops the replay untouched, because an outage is no message's fault. The replay can re-deliver a record the failed flush had already accepted — delivery stays at-least-once, which consumers already assume.
Source code in core/spakky-outbox/src/spakky/outbox/relay/relay.py
AsyncOutboxRelayBackgroundService(storage, transport, config)
¶
Bases: AbstractAsyncBackgroundService
Polls the Outbox storage and relays pending messages to the transport (async).
Initialize with async storage, transport, and config dependencies.
Source code in core/spakky-outbox/src/spakky/outbox/relay/relay.py
initialize_async()
async
¶
dispose_async()
async
¶
run_async()
async
¶
Poll the outbox storage and relay pending messages asynchronously.
Source code in core/spakky-outbox/src/spakky/outbox/relay/relay.py
__register_refusal(message, halted_partition_keys)
async
¶
Spend one retry on a refused message, or abandon it when none is left.
A message whose budget is spent will never be fetched again, so it is abandoned rather than left behind: that releases its partition key, which would otherwise wait forever on a message nobody will retry, and keeps the record findable. A message that still has budget holds its key back until it is delivered, so nothing of that key overtakes it.
Source code in core/spakky-outbox/src/spakky/outbox/relay/relay.py
__publish_one_at_a_time(messages, halted_partition_keys)
async
¶
Replay a batch whose flush failed, confirming one message at a time.
A batch flush reports that the broker refused something but not what, so neither the retry budget nor the partition key hold-back can be aimed without replaying. Confirming one message per flush puts the broker's verdict on the message that earned it: the refused message spends its budget and holds back the rest of its key, while its neighbours publish. Only a refusal counts — a transport that cannot reach the broker at all stops the replay untouched, because an outage is no message's fault. The replay can re-deliver a record the failed flush had already accepted — delivery stays at-least-once, which consumers already assume.
Source code in core/spakky-outbox/src/spakky/outbox/relay/relay.py
공통¶
Outbox configuration.
OutboxConfig()
¶
Bases: BaseSettings
Outbox plugin configuration loaded from environment variables.
Load outbox configuration from environment variables.
Source code in core/spakky-outbox/src/spakky/outbox/common/config.py
Outbox message model.
OutboxMessage(id, event_name, payload, headers, created_at, published_at=None, retry_count=0, claimed_at=None, partition_key=None, abandoned_at=None)
dataclass
¶
Persistence-agnostic Outbox message model.
에러¶
Outbox error classes.