Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Command handlers should be optional by default #30

Closed
slashdotdash opened this issue Nov 17, 2016 · 5 comments
Closed

Command handlers should be optional by default #30

slashdotdash opened this issue Nov 17, 2016 · 5 comments

Comments

@slashdotdash
Copy link
Member

slashdotdash commented Nov 17, 2016

Configuring command dispatch using the Commanded.Commands.Router macro forces you to implement a command handler per command.

defmodule ExampleRouter do
  use Commanded.Commands.Router

  dispatch OpenAccount, to: OpenAccountHandler, aggregate: BankAccount, identity: :account_number
  dispatch DepositMoney, to: DepositMoneyHandler, aggregate: BankAccount, identity: :account_number
end

Often the command handler will just delegate to the aggregate root. It does nothing more and adds no value.

defmodule Commanded.ExampleDomain.DepositMoneyHandler do
  alias Commanded.ExampleDomain.BankAccount
  alias Commanded.ExampleDomain.BankAccount.Commands.DepositMoney

  @behaviour Commanded.Commands.Handler

  def handle(%BankAccount{} = aggregate, %DepositMoney{} = deposit_money) do
    aggregate
    |> BankAccount.deposit(deposit_money)
  end
end

To reduce boilerplate code the command handlers should be optional. The default behaviour will be to dispatch the command to the aggregate.

A proposal for routing without a command handler. The to: key is the aggregate root itself and the aggregate: key is not required.

defmodule ExampleRouter do
  use Commanded.Commands.Router

  dispatch OpenAccount, to: BankAccount, identity: :account_number
  dispatch DepositMoney, to: BankAccount, identity: :account_number
end

By default the command will be dispatched to an aggregate root function named execute. Pattern matching will ensure that it invokes the correct function for a given command struct.

Consider making the command to function mapping configurable. As an example the function could be selected by the name of the command. So a command named OpenAccount maps to the function open_account/2.

It must still be possible to configure a command handler for the cases where they are required. An example would be if you need to request additional data for the command from elsewhere (e.g. external HTTP request).

@Papipo
Copy link

Papipo commented Nov 17, 2016

What about using pattern matching?

def execute(state, %OpenAccount{} = command) do
[...]
end

This forces commands to be defined. There might be a mismatch between the CamelCase command and the snake_case function name.

@slashdotdash
Copy link
Member Author

@Papipo I've updated the issue to use execute/2 as the default function name.

@slashdotdash
Copy link
Member Author

Fixed by #35.

@brendanzab
Copy link

What is the purpose of command handlers if they can just be implemented with pattern matching and exectute/2?

@slashdotdash
Copy link
Member Author

slashdotdash commented Apr 24, 2017

@brendanzab By default you can route commands directly to the aggregate and use pattern matching on its execute function.

I like to keep my aggregate functions pure. Therefore, if I need to pull in external data (e.g. third party system, read model projection) I do this in a command handler. Then pass this additional data to the aggregate.

You don't need to use command handlers if you prefer not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants