Loyalty Exchange

Architecture & decisions

A vertical-slice CQRS / Event-Sourcing system: Ash is the command + read-model surface, Commanded + EventStore are the source of truth, Oban runs durable out-of-band work.

Flow

Ash command
validate input
dispatch
Router = aggregate
stateless · build_event/1
event
EventStore
append-only source of truth
project
Read models
accounts · transfers · postings

A process-manager saga drives reserve → settle → credit → complete, then requests QRC settlement via an Oban job.

Money invariant (Option A)

Stateless aggregates; conservation is enforced by an atomic, CHECK-guarded projector update, with a compensating ReservationRejected event if a race slips past command-time validation. It is impossible to persist a negative balance.

Why event sourcing

Loyalty value is money-like: an append-only event log gives audit, replay, and reconciliation by construction; balances are projections, not the source of truth.

Vertical slices

Each use-case is one file (command + versioned event + build_event/1). Read models live in _lookups; the realtime feed is a projection. Mirrors a proven in-house pattern.

Deliberately deferred

Explicit fee/spread postings, token burning on exchange, and auth-gated admin — scoped out to keep the demo focused; documented in the README.