Commanded v1.0.0 Commanded.Event.Handler behaviour View Source
Defines the behaviour an event handler must implement and provides a convenience macro that implements the behaviour, allowing you to handle only the events you are interested in processing.
You should start your event handlers using a Supervisor to ensure they are restarted on error.
Example
defmodule ExampleHandler do
use Commanded.Event.Handler,
application: ExampleApp,
name: "ExampleHandler"
def handle(%AnEvent{..}, _metadata) do
# ... process the event
:ok
end
end
Start your event handler process (or use a Supervisor):
{:ok, _handler} = ExampleHandler.start_link()
Event handler name
The name you specify is used when subscribing to the event store. Therefore you should not change the name once the handler has been deployed. A new subscription will be created when you change the name, and you event handler will receive already handled events.
You can use the module name of your event handler using the __MODULE__
special form:
defmodule ExampleHandler do
use Commanded.Event.Handler,
application: ExampleApp,
name: __MODULE__
end
Subscription options
You can choose to start the event handler's event store subscription from
:origin
, :current
position, or an exact event number using the
start_from
option. The default is to use the origin so your handler will
receive all events.
Use the :current
position when you don't want newly created event handlers
to go through all previous events. An example would be adding an event handler
to send transactional emails to an already deployed system containing many
historical events.
Example
Set the start_from
option (:origin
, :current
, or an explicit event
number) when using Commanded.Event.Handler
:
defmodule ExampleHandler do
use Commanded.Event.Handler,
application: ExampleApp,
name: "ExampleHandler",
start_from: :origin
end
You can optionally override :start_from
by passing it as option when
starting your handler:
{:ok, _handler} = ExampleHandler.start_link(start_from: :current)
Subscribing to an individual stream
By default event handlers will subscribe to all events appended to any stream.
Provide a subscribe_to
option to subscribe to a single stream.
defmodule ExampleHandler do
use Commanded.Event.Handler,
application: ExampleApp,
name: __MODULE__,
subscribe_to: "stream1234"
end
This will ensure the handler only receives events appended to that stream.
init/0
callback
You can define an init/0
function in your handler to be called once it has
started and successfully subscribed to the event store.
This callback function must return :ok
, any other return value will
terminate the event handler with an error.
defmodule ExampleHandler do
use Commanded.Event.Handler,
application: ExampleApp,
name: "ExampleHandler"
def init do
# optional initialisation
:ok
end
def handle(%AnEvent{..}, _metadata) do
# ... process the event
:ok
end
end
error/3
callback
You can define an error/3
callback function to handle any errors returned
from your event handler's handle/2
functions. The error/3
function is
passed the actual error (e.g. {:error, :failure}
), the failed event, and a
failure context.
Use pattern matching on the error and/or failed event to explicitly handle certain errors or events. You can choose to retry, skip, or stop the event handler after an error.
The default behaviour if you don't provide an error/3
callback is to stop
the event handler using the exact error reason returned from the handle/2
function. You should supervise event handlers to ensure they are correctly
restarted on error.
Example error handling
defmodule ExampleHandler do
use Commanded.Event.Handler,
application: ExampleApp,
name: __MODULE__
require Logger
alias Commanded.Event.FailureContext
def handle(%AnEvent{}, _metadata) do
# simulate event handling failure
{:error, :failed}
end
def error({:error, :failed}, %AnEvent{} = event, %FailureContext{context: context}) do
context = record_failure(context)
case Map.get(context, :failures) do
too_many when too_many >= 3 ->
# skip bad event after third failure
Logger.warn(fn -> "Skipping bad event, too many failures: " <> inspect(event) end)
:skip
_ ->
# retry event, failure count is included in context map
{:retry, context}
end
end
defp record_failure(context) do
Map.update(context, :failures, 1, fn failures -> failures + 1 end)
end
end
Consistency
For each event handler you can define its consistency, as one of either
:strong
or :eventual
.
This setting is used when dispatching commands and specifying the
consistency
option.
When you dispatch a command using :strong
consistency, after successful
command dispatch the process will block until all event handlers configured to
use :strong
consistency have processed the domain events created by the
command. This is useful when you have a read model updated by an event handler
that you wish to query for data affected by the command dispatch. With
:strong
consistency you are guaranteed that the read model will be
up-to-date after the command has successfully dispatched. It can be safely
queried for data updated by any of the events created by the command.
The default setting is :eventual
consistency. Command dispatch will return
immediately upon confirmation of event persistence, not waiting for any event
handlers.
Example
defmodule ExampleHandler do
use Commanded.Event.Handler,
application: ExampleApp,
name: "ExampleHandler",
consistency: :strong
end
Link to this section Summary
Functions
Macro as a convenience for defining an event handler.
Returns a specification to start this module under a supervisor.
Callbacks
Called when an event handle/2
callback returns an error.
Event handler behaviour to handle a domain event and its metadata.
Optional initialisation callback function called when the handler starts.
Link to this section Types
subscribe_from()
View Sourcesubscribe_from() :: :origin | :current | non_neg_integer()
Link to this section Functions
Macro as a convenience for defining an event handler.
Returns a specification to start this module under a supervisor.
See Supervisor
.
Link to this section Callbacks
error(error, failed_event, failure_context)
View Sourceerror( error :: term(), failed_event :: domain_event(), failure_context :: Commanded.Event.FailureContext.t() ) :: {:retry, context :: map()} | {:retry, delay :: non_neg_integer(), context :: map()} | :skip | {:stop, reason :: term()}
Called when an event handle/2
callback returns an error.
The error/3
function allows you to control how event handling failures
are handled. The function is passed the error returned by the event handler
(e.g. {:error, :failure}
), the event causing the error, and a context map
containing state passed between retries. Use the context map to track any
transient state you need to access between retried failures.
You can return one of the following responses depending upon the error severity:
{:retry, context}
- retry the failed event, provide a context map containing any state passed to subsequent failures. This could be used to count the number of failures, stopping after too many.{:retry, delay, context}
- retry the failed event, after sleeping for the requested delay (in milliseconds). Context is a map as described in{:retry, context}
above.:skip
- skip the failed event by acknowledging receipt.{:stop, reason}
- stop the event handler with the given reason.
handle(domain_event, metadata)
View Sourcehandle(domain_event(), metadata()) :: :ok | {:error, :already_seen_event} | {:error, reason :: any()}
Event handler behaviour to handle a domain event and its metadata.
Return :ok
on success, {:error, :already_seen_event}
to ack and skip the
event, or {:error, reason}
on failure.
Optional initialisation callback function called when the handler starts.
Can be used to start any related processes when the event handler is started.
Return :ok
on success, or {:stop, reason}
to stop the handler process.