Outbox (Outbox v0.1.0-beta.2)

Copy Markdown View Source

Public facade for the Outbox transactional event bus.

Producers call publish/2 from inside their domain transactions to emit events that subscribers (registered in application config) react to via Outbox.SubscriberJob after the dispatcher fans them out.

See Outbox.Subscriber for the subscriber contract and the README for the full architecture overview.

Summary

Types

Event name. Convention: <entity>.<past-tense-verb> (lowercase, dot-separated).

JSON-serializable payload. Atom keys are converted to strings on insert.

Functions

Returns a Supervisor child_spec for Outbox.Application.

Clears the current process's ambient context.

Returns the current process's ambient context map (defaults to %{}).

Publish a domain event.

Merge map into the ambient context for the current process.

Types

name()

@type name() :: String.t()

Event name. Convention: <entity>.<past-tense-verb> (lowercase, dot-separated).

payload()

@type payload() :: map()

JSON-serializable payload. Atom keys are converted to strings on insert.

Functions

child_spec(opts \\ [])

@spec child_spec(keyword()) :: Supervisor.child_spec()

Returns a Supervisor child_spec for Outbox.Application.

clear_context()

@spec clear_context() :: :ok

Clears the current process's ambient context.

get_context()

@spec get_context() :: map()

Returns the current process's ambient context map (defaults to %{}).

publish(name, payload, opts \\ [])

@spec publish(name(), payload(), keyword()) ::
  {:ok, Outbox.OutboxEvent.t()}
  | {:ok, :sampled_out | :transient}
  | {:error, Ecto.Changeset.t()}
  | {:error, {:schema, term()}}

Publish a domain event.

This function performs a single Repo.insert/1 and does not open its own transaction. The caller is responsible for wrapping the domain write and the publish/2 call in Repo.transaction/1 if atomicity-with-the-domain-write is required (it almost always is).

Atom keys in the payload are converted to strings so subscribers always see string keys (consistent with what JSONB round-trips produce).

Options

  • :context — opaque map merged over the ambient process context (see put_context/1).
  • :sample — float in 0.0..1.0. The event is kept with this probability; when dropped, nothing is persisted or broadcast and {:ok, :sampled_out} is returned. For high-volume telemetry.
  • :transient — when true, the event is not persisted: it is broadcast to the configured PubSub (if any) and {:ok, :transient} is returned. No row, no Oban fan-out, no delivery guarantee — for loss-tolerant signals only, never an audit system of record.

If a validator is registered for this event name (see Outbox.Config.schemas/0), the stringified payload is validated first; a failure returns {:error, {:schema, reason}} and nothing is persisted or broadcast.

Examples

Repo.transaction(fn ->
  {:ok, product} = Repo.insert(changeset)
  {:ok, _event} = Outbox.publish("product.created", %{"id" => product.id})
  product
end)

put_context(map)

@spec put_context(map()) :: :ok

Merge map into the ambient context for the current process.

The ambient context is attached to every event published from this process (unless a per-call :context overrides a key). Set it once per request (e.g. in a plug or LiveView on_mount) so callers don't thread context through every publish/3. The library treats the map as opaque — hosts decide its keys (e.g. actor_id, actor_type). Atom keys are stringified.