DDD.Outbox.Poller (vicander v0.1.1)

Copy Markdown View Source

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):

  1. Claim. A batch of due events is claimed with a lease (locked_until) using FOR UPDATE SKIP LOCKED, so several pollers or nodes never take the same event.
  2. 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.
  3. 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_retries retries the event is dead-lettered (failed_at is 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 with handlers_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 number retry (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

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

poll_now()

Run a poll cycle now and return the number of events claimed (useful in tests).

start_link(opts)