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
endEmitting 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
@type t() :: %{ __struct__: atom(), id: String.t(), type: String.t(), data: map(), metadata: Orkestra.Metadata.t(), occurred_at: DateTime.t() }
Callbacks
@callback field_definitions() :: [field_definition()]
Functions
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.
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.
Declares an event field.
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) andCode.ensure_loaded?/1; - returns
{:ok, module}only when the module is loaded and implements theOrkestra.Eventbehaviour (exportsfield_definitions/0); - returns
:errorfor anything else — an unknown type, asnapshot-*event, a foreign event, or a module that is not an Orkestra event.
Never raises.