Orkestra.Event behaviour (orkestra v0.2.3)

Copy Markdown View Source

Behaviour and struct builder for domain events.

An event represents something that happened in the system. Events are immutable facts — they are never rejected or retried.

Defining an event

defmodule MyApp.Tasks.Events.AssessmentCompleted do
  use Orkestra.Event

  field :task_id, :string, required: true
  field :expert_name, :string, required: true
  field :action_name, :string, required: true
  field :status, :string, required: true
  field :result, :map, default: %{}
  field :cost_usd, :float, default: 0.0
end

Emitting an event

{:ok, event} = AssessmentCompleted.new(%{
  task_id: "task_123",
  expert_name: "architect",
  action_name: "perform-assessment",
  status: "success",
  result: %{...}
})

# From a command (preserves correlation, sets causation)
{:ok, event} = AssessmentCompleted.from_command(command, %{
  task_id: "task_123",
  ...
})

Summary

Functions

Normalizes the first-level keys of a stored event data map back to the atom-keyed contract produced by new/2.

Atomizes only the first-level keys of map whose string form is one of known_atoms. Keys already atoms are kept as-is; any other key (a string not in known_atoms, or any non-string key) and every value are left unchanged.

Declares an event field.

Resolves the event module named by a stored type string (e.g. "MyApp.Events.Created").

Types

field_definition()

@type field_definition() :: {atom(), atom(), keyword()}

t()

@type t() :: %{
  __struct__: atom(),
  id: String.t(),
  type: String.t(),
  data: map(),
  metadata: Orkestra.Metadata.t(),
  occurred_at: DateTime.t()
}

Callbacks

field_definitions()

@callback field_definitions() :: [field_definition()]

Functions

atomize_data(type, data)

@spec atomize_data(String.t(), map()) :: map()

Normalizes the first-level keys of a stored event data map back to the atom-keyed contract produced by new/2.

Given the event type string and a data map (possibly string-keyed — e.g. as decoded from JSON by the EventStoreDB adapter), atomizes only the top-level keys whose string form matches a declared field of the resolved event module. Every other key, and all values (including nested maps), are left untouched.

Contract (deliberately narrow, to stay safe and predictable):

  • If the module cannot be resolved (unknown type, snapshot, non-event), the map is returned unchanged (string keys preserved). Never raises.
  • No dynamic atom creation: the target atoms are the already-existing field atoms declared at compile time.
  • Shallow only: nested values are NOT recursed into. Converting nested enums / datetimes / value objects is the domain's responsibility, not the transport layer's.

atomize_known_keys(map, known_atoms)

@spec atomize_known_keys(map(), [atom()]) :: map()

Atomizes only the first-level keys of map whose string form is one of known_atoms. Keys already atoms are kept as-is; any other key (a string not in known_atoms, or any non-string key) and every value are left unchanged.

Creates no new atoms — the atomized keys come exclusively from known_atoms. Idempotent: applying it to an already-normalized map is a no-op.

field(name, type, opts \\ [])

(macro)

Declares an event field.

generate_id()

resolve_module(type)

@spec resolve_module(String.t()) :: {:ok, module()} | :error

Resolves the event module named by a stored type string (e.g. "MyApp.Events.Created").

Safe by construction:

  • uses String.to_existing_atom/1 (never creates a new atom) and Code.ensure_loaded?/1;
  • returns {:ok, module} only when the module is loaded and implements the Orkestra.Event behaviour (exports field_definitions/0);
  • returns :error for anything else — an unknown type, a snapshot-* event, a foreign event, or a module that is not an Orkestra event.

Never raises.