Created
November 17, 2022 11:53
-
-
Save dvic/38fa0a426311d9dba7186e2d88901395 to your computer and use it in GitHub Desktop.
an improved aggregate case for commanded
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule AggregateCase do | |
@moduledoc false | |
use ExUnit.CaseTemplate | |
alias Commanded.Aggregate.Multi | |
using opts do | |
aggregate_module = Keyword.fetch!(opts, :aggregate) | |
quote do | |
defp run_commands(commands) do | |
unquote(__MODULE__).run_commands(unquote(aggregate_module), [], commands) | |
end | |
defp run_commands(initial_events, commands) do | |
unquote(__MODULE__).run_commands(unquote(aggregate_module), initial_events, commands) | |
end | |
defp run_commands(aggregate_or_module, initial_events, commands) do | |
unquote(__MODULE__).run_commands(aggregate_or_module, initial_events, commands) | |
end | |
end | |
end | |
def run_commands(aggregate_or_module, initial_events, commands) do | |
struct(aggregate_or_module) | |
|> evolve(initial_events) | |
|> execute(commands) | |
end | |
defp evolve(%aggregate_module{} = aggregate, events) do | |
events | |
|> List.wrap() | |
|> Enum.reduce(aggregate, fn event, aggregate -> | |
next = aggregate_module.apply(aggregate, event) | |
if not match?(%^aggregate_module{}, next) do | |
raise "apply/2 returned invalid value: #{inspect(next)}" | |
end | |
next | |
end) | |
end | |
defp execute(%{} = aggregate, commands) do | |
{aggregate, reversed_events, error} = | |
commands | |
|> List.wrap() | |
|> Enum.reduce_while( | |
{aggregate, [], nil}, | |
fn command, {aggregate, events, _error} -> | |
result = command_result(aggregate, command) | |
case result do | |
{:error, _reason} = error -> | |
{:halt, {aggregate, events, error}} | |
{:ok, new_aggregate, command_events} -> | |
new_events = [command_events | events] | |
{:cont, {new_aggregate, new_events, nil}} | |
end | |
end | |
) | |
events = List.flatten(reversed_events) |> Enum.reverse() | |
{aggregate, events, error} | |
end | |
defguardp event_or_events(v) when is_struct(v) or is_list(v) | |
defp command_result(aggregate, command) do | |
case execute_command(aggregate, command) do | |
{:error, _reason} = error -> | |
error | |
%Multi{} = multi -> | |
case Multi.run(multi) do | |
{:error, _reason} = error -> | |
error | |
{new_aggregate, multi_events} -> | |
{:ok, new_aggregate, multi_events} | |
end | |
{:ok, command_events} when event_or_events(command_events) -> | |
new_aggregate = evolve(aggregate, command_events) | |
{:ok, new_aggregate, command_events} | |
command_events when event_or_events(command_events) -> | |
new_aggregate = evolve(aggregate, command_events) | |
{:ok, new_aggregate, command_events} | |
end | |
end | |
defp execute_command(%aggregate_module{} = aggregate, %command_module{} = command) do | |
function_name = Module.split(command_module) |> List.last() |> Macro.underscore() |> String.to_atom() | |
if Kernel.function_exported?(aggregate_module, function_name, 2) do | |
Kernel.apply(aggregate_module, function_name, [aggregate, command]) | |
else | |
aggregate_module.execute(aggregate, command) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment