View Source Sorcery.Domain (Sorcery v0.1.0)

A powerful macro for defining event-sourced domains in a declarative way.

Domains in Sorcery represent your business entities and their rules. Each domain:

  • Defines its state shape
  • Declares what events can occur
  • Specifies how events change state
  • Handles commands that generate events

Domain Structure

A domain consists of:

  1. State - The current data structure of your domain
  2. Events - Things that have happened (facts)
  3. Commands - User intentions that may generate events
  4. Event Handlers - Functions that update state based on events
  5. Command Handlers - Functions that validate commands and generate events

Auto-Starting

Domains are automatically started when your application boots. This ensures that:

  1. All domains are ready to handle commands immediately
  2. Domains listening for events from other domains don't miss any events
  3. Event handlers are always active

Example

defmodule MyApp.UserDomain do
  use Sorcery.Domain

  domain do
    # Define the state shape
    state :name, default: nil
    state :email, default: nil
    state :status, default: :inactive

    # Define an event
    event :user_registered do
      field :name, type: :string
      field :email, type: :string

      # How this event changes state
      apply fn state, event ->
        %{state |
          name: event.name,
          email: event.email,
          status: :active
        }
      end
    end

    # Define a command
    command :register_user do
      field :name, type: :string
      field :email, type: :string

      # Command validation and event generation
      handle fn state, command ->
        cond do
          state.status == :active ->
            {:error, :already_registered}
          !valid_email?(command.email) ->
            {:error, :invalid_email}
          true ->
            event = %__MODULE__.Events.UserRegistered{
              name: command.name,
              email: command.email
            }
            {:ok, [event]}
        end
      end
    end
  end
end

State Definition

State fields are defined using the state/2 macro:

state :name, default: nil
state :email, default: nil
state :status, default: :inactive

Event Definition

Events are defined using the event/2 macro and must include:

  • Fields that the event carries
  • An apply function that updates state
event :user_registered do
  field :name, type: :string
  field :email, type: :string

  apply fn state, event ->
    %{state | name: event.name, email: event.email}
  end
end

Command Definition

Commands are defined using the command/2 macro and must include:

  • Fields that the command requires
  • A handle function that validates and generates events
command :register_user do
  field :name, type: :string
  field :email, type: :string

  handle fn state, command ->
    # Return {:ok, [events]} on success
    # Return {:error, reason} on failure
    {:ok, [%Events.UserRegistered{...}]}
  end
end

External Events

You can handle events from other domains using external_event/2:

external_event OtherDomain.Events.SomethingHappened do
  apply fn state, event ->
    # Update state based on external event
    state
  end
end

Best Practices

  1. Keep Domains Focused: Each domain should represent a single business concept
  2. Events are Facts: Name events in past tense, they represent things that happened
  3. Commands May Fail: Always handle validation in command handlers
  4. State is Private: Only expose state changes through commands
  5. Event Handlers are Pure: They should only transform state, no side effects

Summary

Functions

Defines how an event changes the domain state.

Defines a command that can be executed on the domain.

Defines a domain block where state, events, and commands are declared.

Defines an event type and its structure.

Defines handling of events from other domains.

Declares a field for an event or command.

Defines how to handle a command or event.

Declares a state field with an optional default value.

Functions

apply(func)

(macro)

Defines how an event changes the domain state.

The function receives the current state and event as arguments and should return the new state.

Example

apply fn state, event ->
  %{state | name: event.name, email: event.email}
end

command(name, list)

(macro)

Defines a command that can be executed on the domain.

Commands must include:

Example

command :register_user do
  field :name, type: :string
  field :email, type: :string

  handle fn state, command ->
    {:ok, [%Events.UserRegistered{...}]}
  end
end

domain(list)

(macro)

Defines a domain block where state, events, and commands are declared.

Example

domain do
  state :name, default: nil
  event :user_registered do
    # ...
  end
  command :register_user do
    # ...
  end
end

event(name, list)

(macro)

Defines an event type and its structure.

Events must include:

Example

event :user_registered do
  field :name, type: :string
  field :email, type: :string

  apply fn state, event ->
    %{state | name: event.name, email: event.email}
  end
end

external_event(event_struct, list)

(macro)

Defines handling of events from other domains.

This is useful when your domain needs to react to events from other parts of your system.

Example

external_event OtherDomain.Events.SomethingHappened do
  apply fn state, event ->
    # Update state based on external event
    state
  end
end

field(name, opts)

(macro)

Declares a field for an event or command.

Options

  • :type - The type of the field (e.g., :string, :integer)

Example

field :name, type: :string
field :age, type: :integer

handle(func)

(macro)

Defines how to handle a command or event.

For commands, the function should return:

  • {:ok, events} on success where events is a list of events to apply
  • {:error, reason} on failure

Example

handle fn state, command ->
  if valid?(command) do
    {:ok, [%Events.SomethingHappened{...}]}
  else
    {:error, :invalid_command}
  end
end

state(name, opts \\ [])

(macro)

Declares a state field with an optional default value.

Options

  • :default - The default value for this field

Example

state :name, default: nil
state :status, default: :pending