Counterpoint.Command behaviour (counterpoint v0.1.0)

Copy Markdown View Source

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
end

Execute with Counterpoint.CommandRunner.run/3.

Summary

Callbacks

Execute the command.

Callbacks

run(command, ra)

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