Behaviour for commands that require external dependencies (effects) at runtime.
Identical to Counterpoint.Command except run/3 receives a deps term in
addition to the ReadAppender. Inject anything that shouldn't be hard-coded
in the command module: HTTP clients, email senders, random-ID generators, etc.
This keeps commands unit-testable by swapping deps in tests.
Use Counterpoint.CommandWithEffectRunner to execute these commands.
Example
defmodule MyApp.Commands.SendWelcomeEmail do
use Counterpoint.CommandWithEffect
import Counterpoint.ReadAppender
alias Counterpoint.Query
alias MyApp.Events.{UserRegistered, WelcomeEmailSent}
defstruct [:user_id]
@impl Counterpoint.CommandWithEffect
def run(%__MODULE__{user_id: id}, ra, %{mailer: mailer}) do
{events, ra} =
read_events(ra, Query.new() |> Query.add_item(types: [UserRegistered], tags: ["user_id:#{id}"]))
case events do
[] -> {:error, :user_not_found}
[%{data: %UserRegistered{email: email}} | _] ->
mailer.send(email, "Welcome!")
append_event(ra, %WelcomeEmailSent{user_id: id})
end
end
end
runner = Counterpoint.CommandWithEffectRunner.new(:my_store, %{mailer: MyApp.Mailer})
Counterpoint.CommandWithEffectRunner.run(runner, %MyApp.Commands.SendWelcomeEmail{user_id: "42"})
Summary
Callbacks
Execute the command with external dependencies.
Callbacks
@callback run(command :: struct(), ra :: Counterpoint.ReadAppender.t(), deps :: term()) :: {:ok, term()} | {:error, term()}
Execute the command with external dependencies.
ra is a fresh Counterpoint.ReadAppender. deps is whatever term was
passed to CommandWithEffectRunner.new/2.