Behaviour for write-side commands.
A command reads relevant events via Counterpoint.ReadAppender, applies
business rules, and appends new events if the operation is valid. The
ReadAppender automatically tracks read positions, enabling optimistic
concurrency: the append will fail with {:error, :append_condition_failed}
if another writer modified the relevant stream since the read.
Counterpoint.CommandRunner handles retries transparently.
use Counterpoint.Command injects @behaviour Counterpoint.Command.
Example
defmodule MyApp.Commands.PlaceOrder do
use Counterpoint.Command
import Counterpoint.ReadAppender
alias Counterpoint.Query
alias MyApp.Events.OrderPlaced
defstruct [:order_id, :total]
@impl Counterpoint.Command
def run(%__MODULE__{order_id: id, total: total}, ra) do
{existing, ra} =
read_events(ra, Query.new() |> Query.add_item(types: [OrderPlaced], tags: ["order_id:#{id}"]))
if Enum.any?(existing) do
{:error, :already_placed}
else
append_event(ra, %OrderPlaced{order_id: id, total: total})
end
end
endExecute with Counterpoint.CommandRunner.run/3.
Summary
Callbacks
Execute the command.
Callbacks
@callback run(command :: struct(), ra :: Counterpoint.ReadAppender.t()) :: {:ok, term()} | {:error, term()}
Execute the command.
ra is a fresh Counterpoint.ReadAppender. Use ReadAppender.read_events/2
to load state and ReadAppender.append_event/2 or append_events/2 to
persist new events.