Counterpoint.CommandWithEffect behaviour (counterpoint v0.1.0)

Copy Markdown View Source

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

run(command, ra, deps)

@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.