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:
- State - The current data structure of your domain
- Events - Things that have happened (facts)
- Commands - User intentions that may generate events
- Event Handlers - Functions that update state based on events
- Command Handlers - Functions that validate commands and generate events
Auto-Starting
Domains are automatically started when your application boots. This ensures that:
- All domains are ready to handle commands immediately
- Domains listening for events from other domains don't miss any events
- 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
endState Definition
State fields are defined using the state/2 macro:
state :name, default: nil
state :email, default: nil
state :status, default: :inactiveEvent 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
endCommand 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
endExternal 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
endBest Practices
- Keep Domains Focused: Each domain should represent a single business concept
- Events are Facts: Name events in past tense, they represent things that happened
- Commands May Fail: Always handle validation in command handlers
- State is Private: Only expose state changes through commands
- 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
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
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
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
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
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
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
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
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