Commanded v0.19.1 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, 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,
    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,
    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,
    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, 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, 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,
    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

Link to this type

consistency()

View Source
consistency() :: :eventual | :strong
Link to this type

domain_event()

View Source
domain_event() :: struct()
Link to this type

metadata()

View Source
metadata() :: map()
Link to this type

subscribe_from()

View Source
subscribe_from() :: :origin | :current | non_neg_integer()

Link to this section Functions

Link to this macro

__using__(opts)

View Source (macro)

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

Link to this callback

error(error, failed_event, failure_context)

View Source
error(
  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.

Link to this callback

handle(domain_event, metadata)

View Source
handle(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.

Link to this callback

init()

View Source
init() :: :ok | {:stop, reason :: any()}

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.