Packages
x3m_system
0.9.1
0.9.1
0.9.0
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
retired
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
retired
0.7.7
0.7.6
retired
0.7.5
0.7.4
retired
0.7.3
retired
0.7.2
0.7.1
0.7.0
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
retired
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.1.1
0.1.0
Building blocks for distributed and/or CQRS/ES systems
Current section
Files
Jump to
Current section
Files
lib/message_handler/unload.ex
defmodule X3m.System.MessageHandler.Unload do
@moduledoc !"""
Internal. Decides whether and when an aggregate process is torn down after a
command, from a message handler's `:unload_aggregate_on` rules (keyed on emitted
events and/or current state).
Extracted from the `X3m.System.MessageHandler` macro on purpose: here the rules
are an ordinary `map()` argument, so the compiler does not narrow them to the
empty-map literal a consumer without `:unload_aggregate_on` would otherwise inline
(which trips the type checker on the optional `:events`/`:state` lookups).
"""
alias X3m.System.Message
@doc """
Applies the `unload_aggregate_on` `rules` for a just-committed `message`.
`rules` is the handler's `:unload_aggregate_on` map, with optional keys:
* `:events` - `%{event_struct_module => {:in, milliseconds}}`; for each emitted event whose
struct matches a key, a delayed `{:exit_process, pid, reason}` is sent to `pid_facade_name`.
* `:state` - a 1-arity function of the new `client_state` returning `:skip`, `:unload` or
`{:in, milliseconds}`.
Schedules the event-based teardowns as a side effect and returns the state-based decision for
the caller to act on (`:skip` keeps the process, `:unload` tears it down now, `{:in, ms}`
after `ms`). A `FunctionClauseError` raised by the `:state` function is treated as `:skip`.
"""
@spec decide(
rules :: map(),
message :: Message.t(),
client_state :: term(),
pid :: pid(),
pid_facade_name :: atom()
) :: :skip | :unload | {:in, pos_integer()}
def decide(rules, %Message{} = message, client_state, pid, pid_facade_name) do
rules
|> Map.get(:events, %{})
|> Enum.each(fn {event, time} ->
_schedule_on_events(message.events, event, time, pid, pid_facade_name)
end)
rules
|> Map.get(:state)
|> _decide_on_state(client_state)
end
defp _decide_on_state(fun, client_state) when is_function(fun) do
fun.(client_state)
rescue
FunctionClauseError -> :skip
end
defp _decide_on_state(_state_rule, _client_state),
do: :skip
defp _schedule_on_events([], _event, _time, _pid, _pid_facade_name),
do: :ok
defp _schedule_on_events([%{__struct__: event} | rest], event, time, pid, pid_facade_name) do
_create_scheduler(pid, time, event, pid_facade_name)
_schedule_on_events(rest, event, time, pid, pid_facade_name)
end
defp _schedule_on_events([_other | rest], event, time, pid, pid_facade_name),
do: _schedule_on_events(rest, event, time, pid, pid_facade_name)
defp _create_scheduler(pid, {:in, milliseconds}, event, pid_facade_name) do
reason = "Scheduled tear down of aggregate on #{inspect(event)}"
Process.send_after(pid_facade_name, {:exit_process, pid, reason}, milliseconds)
end
end