Skip to content

Instantly share code, notes, and snippets.

@slashdotdash
Last active September 2, 2022 12:51
Show Gist options
  • Save slashdotdash/811d754951b2573a82ff14fe8506012e to your computer and use it in GitHub Desktop.
Save slashdotdash/811d754951b2573a82ff14fe8506012e to your computer and use it in GitHub Desktop.
Commanded middleware to enrich commands during dispatch, such as calling an external API.

Commanded middleware for command enrichment

Usage

Add the EnrichCommand middleware to your command router:

defmodule MyApp.Router do
  use Commanded.Commands.Router
  
  middleware Middleware.EnrichCommand
end

Implement the CommandEnrichment protocol for any command which needs external data, such as an external API or read model query.

defimpl CommandEnrichment, for: ExampleCommand do
  @doc """
  Enrich command during dispatch, but before aggregate handling.
  """
  def enrich(%ExampleCommand{} = command) do
    %ExampleCommand{id: id} = command

    command = %ExampleCommand{
      command
      | external_data: lookup_external_data(id)
    }

    {:ok, command}
  end
  
  defp lookup_external_data(id) do
    # .. fetch data
  end
end

The enrich/1 function should return {:ok, command} on success, or {:error, error} on failure.

defprotocol CommandEnrichment do
@doc """
Enrich a command with additional data during dispatch, before passing to aggregate.
As an example, this is an extension point where additional data could be retreived
from the database to enrich the command's fields.
"""
@fallback_to_any true
def enrich(command)
end
defimpl CommandEnrichment, for: Any do
@doc """
By default the command is not modified.
"""
def enrich(command), do: {:ok, command}
end
defmodule Middleware.EnrichCommand do
@behaviour Commanded.Middleware
alias Commanded.Middleware.Pipeline
@doc """
Enrich the command via the opt-in command enrichment protocol.
"""
def before_dispatch(%Pipeline{command: command} = pipeline) do
case CommandEnrichment.enrich(command) do
{:ok, command} ->
%Pipeline{pipeline | command: command}
{:error, _error} = reply ->
pipeline
|> Pipeline.respond(reply)
|> Pipeline.halt()
end
end
def after_dispatch(pipeline), do: pipeline
def after_failure(pipeline), do: pipeline
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment