# `DDD.Outbox.Poller`
[🔗](https://github.com/blackuprise/vicander/blob/v0.1.1/lib/ddd/events/outbox/poller.ex#L1)

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

# `child_spec`

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`

---

*Consult [api-reference.md](api-reference.md) for complete listing*
