Forja.Handler behaviour
(Forja v0.4.0)
View Source
Behaviour module for event handlers in Forja.
Implement this behaviour to process events from the Oban processing pipeline.
Each handler specifies which event types it subscribes to via event_types/0.
Callbacks
event_types/0- Returns the list of event types this handler subscribes to, or:allto receive all events.handle_event/2- Called for each event with the event struct and metadata.
Failure Semantics
Handlers must be idempotent — the same event may be delivered more than once in edge cases (e.g., network partitions, consumer crashes). Even if a handler returns an error, the event is considered processed and will not be retried.
When a handler fails, the optional on_failure/3 callback is invoked with the
event, reason, and metadata. Use this to enqueue retries, emit compensating
events, or alert. If on_failure/3 is not implemented, the failure is logged
and telemetry is emitted — no further action is taken.
Example
defmodule MyApp.UserEventHandler do
@behaviour Forja.Handler
@impl true
def event_types, do: ["user.created", "user.updated"]
@impl true
def handle_event(%Forja.Event{} = event, meta) do
# Process the event...
:ok
end
end
Summary
Callbacks
Returns the list of event types this handler subscribes to, or :all for all events.
Handles a single event from the pipeline.
Called when handle_event/2 returns {:error, reason} or raises an exception.
Callbacks
@callback event_types() :: [String.t()] | :all
Returns the list of event types this handler subscribes to, or :all for all events.
Examples
iex> MyHandler.event_types()
["user.created", "user.updated"]
iex> WildcardHandler.event_types()
:all
@callback handle_event(event :: Forja.Event.t(), meta :: map()) :: :ok | {:error, term()}
Handles a single event from the pipeline.
The event is considered processed regardless of the return value. If you need to
handle permanent failures, use Forja.DeadLetter to notify an error handler.
Arguments
event- TheForja.Eventstruct being deliveredmeta- Map containing delivery metadata::forja_name- The Forja instance atom:path- Processing path (:oban,:reconciliation,:inline):correlation_id- UUID grouping all events in the same logical transaction:causation_id- UUID of the event that caused this one (nil for root events)
When you call
Forja.emit/3inside a handler,correlation_idandcausation_idare propagated automatically — you do NOT need to pass them manually.
Examples
iex> handler.handle_event(%Forja.Event{type: "user.created"}, %{})
:ok
iex> handler.handle_event(%Forja.Event{type: "user.created"}, %{deliveries: 3})
{:error, :some_failure}
@callback on_failure(event :: Forja.Event.t(), reason :: term(), meta :: map()) :: :ok | term()
Called when handle_event/2 returns {:error, reason} or raises an exception.
The reason is one of:
{:error, term()}— handler returned an error{:raised, Exception.t()}— handler raised an exception
Use this to enqueue retries, emit compensating events, or alert external systems. This callback is optional — if not implemented, failures are only logged and emitted as telemetry.
Example
@impl true
def on_failure(%Forja.Event{} = event, {:error, :timeout}, _meta) do
# Enqueue a retry as a dedicated Oban worker
%{event_id: event.id}
|> MyApp.Workers.RetryNotification.new()
|> Oban.insert()
end
def on_failure(_event, _reason, _meta), do: :ok