SOIT Docs
Architecture

Outbox Events

Use transactional outbox events for reliable SOIT runtime state changes.

SOIT uses a transactional outbox to persist domain facts before dispatching side effects. The outbox keeps runtime state changes and event publication in the same database transaction, so workers can retry dispatch without losing or duplicating committed facts.

Why the outbox exists

Runtime state often changes before asynchronous workers perform follow-up work — indexing a document, executing a workflow node, delivering a notification. If the state change and the event that triggers the follow-up were written separately, a crash between the two would either lose the event or duplicate the side effect. Committing both in one transaction removes that window.

Core tables

TablePurpose
event_outboxPending and processed domain events
event_consumer_checkpointIdempotency checkpoint per consumer and event
dead_letter_eventsFailed dispatch records that need operator review or replay

Event envelope

Each outbox record stores a canonical envelope that is intentionally small and stable:

  • event_id, event_type, event_version — identify the fact.
  • tenant_id, subject_type, subject_id — scope the affected resource.
  • run_id, task_id, thread_id, workflow_run_id — link back to runtime ledger records.
  • correlation_id, causation_id — preserve causal traceability across event chains.
  • producer, occurred_at, payload — describe the source and data.

Event flow

  1. A service changes domain or runtime state.
  2. The same transaction writes an outbox event.
  3. A dispatcher reads pending outbox events and hands them to workers.
  4. Workers perform side effects through governed adapters.
  5. Runtime state is updated with results, failures, costs, and trace evidence.
  6. Consumers record checkpoints so redelivery is idempotent; undeliverable events land in the dead-letter table for review or replay.

Rules for module authors

Product modules emit facts through the runtime event helpers instead of writing directly to dispatch infrastructure. Events should be versioned, scoped, idempotent where practical, and tied back to tenant, workspace, request, run, and task context whenever they affect execution.

On this page