Commanded v1.0.1 Commanded.Commands.Router behaviour View Source
Command routing macro to allow configuration of each command to its command handler.
Example
Define a router module which uses Commanded.Commands.Router
and configures
available commands to dispatch:
defmodule BankRouter do
use Commanded.Commands.Router
dispatch OpenAccount,
to: OpenAccountHandler,
aggregate: BankAccount,
identity: :account_number
end
The to
option determines which module receives the command being dispatched.
This command handler module must implement a handle/2
function. It receives
the aggregate's state and the command to execute. Usually the command handler
module will forward the command to the aggregate.
Once configured, you can either dispatch a command using the module by and specify the application:
command = %OpenAccount{account_number: "ACC123", initial_balance: 1_000}
:ok = BankRouter.dispatch(command, application: BankApp)
Or, more simply you should include the router module in your application:
defmodule BankApp do
use Commanded.Application, otp_app: :my_app
router MyApp.Router
end
Then dispatch commands using the app:
command = %OpenAccount{account_number: "ACC123", initial_balance: 1_000}
:ok = BankApp.dispatch(command)
Dispatch command directly to an aggregate
You can route a command directly to an aggregate, without requiring an intermediate command handler.
Example
defmodule BankRouter do
use Commanded.Commands.Router
dispatch OpenAccount, to: BankAccount, identity: :account_number
end
The aggregate must implement an execute/2
function that receives the
aggregate's state and the command being executed.
Define aggregate identity
You can define the identity field for an aggregate once using the identify
macro.
The configured identity will be used for all commands registered to the aggregate,
unless overridden by a command registration.
Example
defmodule BankRouter do
use Commanded.Commands.Router
identify BankAccount,
by: :account_number,
prefix: "bank-account-"
dispatch OpenAccount, to: BankAccount
end
An optional identity prefix can be used to distinguish between different
aggregates that would otherwise share the same identity. As an example you
might have a User
and a UserPreferences
aggregate that you wish
to share the same identity. In this scenario you should specify a prefix
for each aggregate (e.g. "user-" and "user-preference-").
The prefix is used as the stream identity when appending and reading the
aggregate's events: "
Consistency
You can choose the consistency guarantee when dispatching a command. The available options are:
:eventual
(default) - don't block command dispatch while waiting for event handlers:ok = BankApp.dispatch(command) :ok = BankApp.dispatch(command, consistency: :eventual)
:strong
- block command dispatch until all strongly consistent event handlers and process managers have successfully processed all events created by the command.Use this when you have event handlers that update read models you need to query immediately after dispatching the command.
:ok = BankApp.dispatch(command, consistency: :strong)
Provide an explicit list of event handler and process manager modules (or their configured names), containing only those handlers you'd like to wait for. No other handlers will be awaited on, regardless of their own configured consistency setting.
:ok = BankApp.dispatch(command, consistency: [ExampleHandler, AnotherHandler]) :ok = BankApp.dispatch(command, consistency: ["ExampleHandler", "AnotherHandler"])
Note you cannot opt-in to strong consistency for a handler that has been configured as eventually consistent.
Dispatch return
By default a successful command dispatch will return :ok
. You can change
this behaviour by specifying a returning
option.
The supported options are:
:aggregate_state
- to return the update aggregate state.:aggregate_version
- to return only the aggregate version.:execution_result
- to return aCommanded.Commands.ExecutionResult
struct containing the aggregate's identity, state, version, and any events produced from the command along with their associated metadata.false
- don't return anything except an:ok
.
Aggregate state
Return the updated aggregate state as part of the dispatch result:
{:ok, %BankAccount{}} = BankApp.dispatch(command, returning: :aggregate_state)
This is useful when you want to immediately return fields from the aggregate's state without requiring an read model projection and waiting for the event(s) to be projected. It may also be appropriate to use this feature for unit tests.
However, be warned that tightly coupling an aggregate's state with read requests may be harmful. It's why CQRS enforces the separation of reads from writes by defining two separate and specialised models.
Aggregate version
You can optionally choose to return the aggregate's version as part of the dispatch result:
{:ok, aggregate_version} = BankApp.dispatch(command, returning: :aggregate_version)
This is useful when you need to wait for an event handler, such as a read model projection, to be up-to-date before querying its data.
Execution results
You can also choose to return the execution result as part of the dispatch result:
alias Commanded.Commands.ExecutionResult
{:ok, %ExecutionResult{} = result} = BankApp.dispatch(command, returning: :execution_result)
Or by setting the default_dispatch_return
in your application config file:
# config/config.exs
config :commanded, default_dispatch_return: :execution_result
Use the execution result struct to get information from the events produced from the command.
Metadata
You can associate metadata with all events created by the command.
Supply a map containing key/value pairs comprising the metadata:
:ok = BankApp.dispatch(command, metadata: %{"ip_address" => "127.0.0.1"})
Link to this section Summary
Functions
Configure the command, or list of commands, to be dispatched to the corresponding handler for a given aggregate.
Define an aggregate's identity
Include the given middleware module to be called before and after success or failure of each command dispatch
Link to this section Functions
Configure the command, or list of commands, to be dispatched to the corresponding handler for a given aggregate.
Example
defmodule BankRouter do
use Commanded.Commands.Router
dispatch [OpenAccount, DepositMoney], to: BankAccount, identity: :account_number
end
Define an aggregate's identity
You can define the identity field for an aggregate using the identify
macro.
The configured identity will be used for all commands registered to the
aggregate, unless overridden by a command registration.
Example
defmodule BankRouter do
use Commanded.Commands.Router
identify BankAccount,
by: :account_number,
prefix: "bank-account-"
end
Include the given middleware module to be called before and after success or failure of each command dispatch
The middleware module must implement the Commanded.Middleware
behaviour.
Middleware modules are executed in the order they are defined.
Example
defmodule BankRouter do
use Commanded.Commands.Router
middleware CommandLogger
middleware MyCommandValidator
middleware AuthorizeCommand
dispatch [OpenAccount, DepositMoney], to: BankAccount, identity: :account_number
end
Link to this section Callbacks
dispatch(struct, keyword)
View Sourcedispatch(struct(), keyword()) :: :ok | {:ok, non_neg_integer()} | {:ok, struct()} | {:error, term()}