How factos_pog Works
factos_pog is the PostgreSQL event-store and durable-delivery backend for
Factos. A bounded context constructs one validated Dispatcher containing its
event codec and complete list of named subscriptions. Construction rejects
blank and exact duplicate subscription names before database IO.
Dispatch flow
new_dispatch creates command-specific consistency configuration around that
dispatcher. Without with_query, one stream revision is the consistency
boundary. With with_query, the boundary is an arbitrary Factos event-type/tag
query.
One PostgreSQL SERIALIZABLE transaction:
- reads and decodes the selected event context;
- folds the context with the decider’s
evolvefunction; - runs the decider;
- verifies the observed stream or query context is still current;
- assigns event ids and inserts produced events and tag-index rows;
- runs every configured pure subscription reactor;
- encodes and inserts the resulting outbox effects;
- commits events and effects atomically.
Serialization and deadlock conflicts retry up to the builder’s configured attempt count. Deciders, codecs, reactors, and event-id generators can therefore run more than once and must remain pure or idempotent.
Event storage
factos_events is append-only. It stores global position, application event id,
stream revision, type, version, tags, metadata, and opaque payload bytes.
factos_event_tags(position, tag) mirrors tags into indexed rows used by context
queries.
Application codecs own payload encoding and decoding. new_proposed requires
the typed event, durable type, schema version, and payload, while defaulting tags
to [] and metadata to factos.empty_metadata(). Codecs optionally replace
those values through with_tags and with_metadata. Any value needed by a
future selective consistency query must be exposed as an event tag.
After validating a stored type and decoding its payload, return
decoded_event(stored, event:). It creates the successful codec result while
preserving the stored type, version, tags, and metadata.
Durable subscriptions
A Subscription combines:
- a stable subscription name;
- a pure
factos.Reactor; - the effect codec for that reactor’s output.
Every configured subscription observes every newly committed event and may
return no effects. The subscription name is persisted separately and also
namespaces effect_key, so heterogeneous subscriptions can safely share one
dispatcher and outbox.
Leasing and settlement
An OutboxConsumer binds one consumer/target identity to one validated
RetryPolicy. Policies start from documented defaults, accept focused builder
overrides, and validate only when built. Leasing selects only pending, available
rows for that identity, increments attempt, and returns an unguessable lease
token.
The worker classifies each attempt with one opaque input value:
delivered();retryable_failure(reason:);permanent_failure(reason:).
settle_outbox performs one lease-guarded update. Retryable failures use capped
exponential backoff with deterministic jitter. They become dead letters when
either the maximum attempt count or maximum delivery age is reached. Permanent
failures dead-letter immediately. Expired, superseded, or mismatched leases
return StaleLease without changing the row.
Applications own failure classification and effect execution. The three constructors classify one leased message; they do not control a worker actor. Applications do not own retry delay calculation, retry budgets, or dead-letter transitions.
Operational recovery
list_dead_letters returns safe envelope metadata and replay history.
replay_dead_letter moves a dead letter back to pending while preserving its
original payload. Operators explicitly choose whether to preserve or reset the
attempt count; replay count and timestamp remain recorded for audit.
Go deeper
- Durable Subscriptions covers production topology configuration and controlled backfill.
- Durable Effects covers worker delivery, settlement, dead-letter recovery, and replay.