GenServer that delivers the events in the domain_events outbox table to their handlers.
Events are written in the same transaction as the aggregate that produced them (see
DDD.Repository.Ecto). The poller then delivers each one to every handler subscribed on
DDD.EventBus (see DDD.EventHandler):
- Claim. A batch of due events is claimed with a lease (
locked_until) usingFOR UPDATE SKIP LOCKED, so several pollers or nodes never take the same event. - Deliver. Handlers run synchronously, each in its own transaction by default,
together with a record in
event_deliveries. A handler that already succeeded for this event is skipped. - Mark. The event is marked published when every handler succeeded or rejected it
(a business error is a recorded rejection, never retried). A system failure
(infrastructure error, exception) is retried after a backoff, and after
:max_retriesretries the event is dead-lettered (failed_atis set, it is no longer claimed, and a[:ddd, :outbox, :dead_letter]telemetry event is emitted).
The attempt is counted when the event is claimed, so an attempt that kills the VM still uses one up. If the server stops mid-delivery, the lease expires and another poll picks the event up again; handlers that had already succeeded are not re-run.
Supervision tree setup
children = [
MyApp.Repo,
DDD.EventBus,
{DDD.Outbox.Poller, repo: MyApp.Repo, interval_ms: 500, batch_size: 100}
]Subscribe the handlers before the poller starts (an event with no subscribed handler is marked published straight away).
Options
:repo— (required) your Ecto.Repo:interval_ms— poll interval in ms (default: 500):batch_size— events claimed per poll (default: 100):event_bus— module withhandlers_for/1(default:DDD.EventBus):lease_ms— how long a claimed event is reserved (default: 30_000):max_retries— retries after the first attempt before dead-lettering (default: 10, so 11 attempts):backoff_ms—fn retry -> ms end, the wait before retry numberretry(default: exponential, 1s doubling up to 5 minutes)
:max_retries and the default backoff can also be set for the whole app, and an option
passed here wins over it:
config :vicander, :outbox, max_retries: 10, backoff_base_ms: 1_000, backoff_max_ms: 300_000
Summary
Functions
Returns a specification to start this module under a supervisor.
Run a poll cycle now and return the number of events claimed (useful in tests).
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
Run a poll cycle now and return the number of events claimed (useful in tests).