-
Notifications
You must be signed in to change notification settings - Fork 237
/
composite_router.ex
130 lines (96 loc) · 3.79 KB
/
composite_router.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
defmodule Commanded.Commands.CompositeRouter do
@moduledoc """
Composite router allows you to combine multiple router modules into a single
router able to dispatch any registered command from an included child router.
One example usage is to define a router per context and then combine each
context's router into a single top-level composite app router used for all
command dispatching.
### Example
Define a composite router module which imports the commands from each included
router:
defmodule Bank.AppRouter do
use Commanded.Commands.CompositeRouter
router(Bank.Accounts.Router)
router(Bank.MoneyTransfer.Router)
end
One or more routers or composite routers can be included in a
`Commanded.Application` since it is also a composite router:
defmodule BankApp do
use Commanded.Application
router(Bank.AppRouter)
end
You can dispatch a command via the application which will then be routed to
the associated child router:
command = %OpenAccount{account_number: "ACC123", initial_balance: 1_000}
:ok = BankApp.dispatch(command)
Or via the composite router itself, specifying the application:
:ok = Bank.AppRouter.dispatch(command, application: BankApp)
A composite router can include composite routers.
"""
defmacro __using__(opts) do
quote do
require Logger
import unquote(__MODULE__)
@before_compile unquote(__MODULE__)
Module.register_attribute(__MODULE__, :registered_commands, accumulate: false)
application = Keyword.get(unquote(opts), :application)
default_dispatch_opts =
unquote(opts)
|> Keyword.get(:default_dispatch_opts, [])
|> Keyword.put(:application, application)
@default_dispatch_opts default_dispatch_opts
@registered_commands %{}
end
end
@doc """
Register a `Commanded.Commands.Router` module within this composite router.
Will allow the composite router to dispatch any commands registered by the
included router module. Multiple routers can be registered.
"""
defmacro router(router_module) do
quote location: :keep do
for command <- unquote(router_module).__registered_commands__() do
case Map.get(@registered_commands, command) do
nil ->
@registered_commands Map.put(@registered_commands, command, unquote(router_module))
existing_router ->
raise "duplicate registration for #{inspect(command)} command, registered in both #{inspect(existing_router)} and #{inspect(unquote(router_module))}"
end
end
end
end
defmacro __before_compile__(_env) do
quote generated: true do
@doc false
def __registered_commands__ do
Enum.map(@registered_commands, fn {command_module, _router} -> command_module end)
end
@doc false
def dispatch(command, opts \\ [])
@doc false
def dispatch(command, :infinity),
do: do_dispatch(command, timeout: :infinity)
@doc false
def dispatch(command, timeout) when is_integer(timeout),
do: do_dispatch(command, timeout: timeout)
@doc false
def dispatch(command, opts),
do: do_dispatch(command, opts)
for {command_module, router} <- @registered_commands do
@command_module command_module
@router router
defp do_dispatch(%@command_module{} = command, opts) do
opts = Keyword.merge(@default_dispatch_opts, opts)
@router.dispatch(command, opts)
end
end
# Catch unregistered commands, log and return an error.
defp do_dispatch(command, _opts) do
Logger.error(fn ->
"attempted to dispatch an unregistered command: " <> inspect(command)
end)
{:error, :unregistered_command}
end
end
end
end