View Source Sorcery
Sorcery is a magical library for building event-sourced applications in Elixir. It is inspired by the Commanded library but aims to provide a simpler, more intuitive API.
Features
- Domain-Driven Design: Define domains as collections of commands and events that encapsulate your business logic
- Event Sourcing: All state changes are captured as a sequence of events, providing a complete audit trail
- Service Layer: Handle external concerns (emails, databases, APIs) cleanly separated from your domain logic
- Automatic Process Management: Domains and Services are automatically managed as GenServers
- Type Safety: Leverages Elixir's type system for robust command and event definitions
- Simple API: Intuitive command execution and event handling
Installation
If available in Hex, the package can be installed
by adding sorcery to your list of dependencies in mix.exs:
def deps do
[
{:sorcery, "~> 0.1.0"}
]
endArchitecture
Sorcery is built around two main concepts:
- Domains: Encapsulate your business logic and maintain state through events
- Services: Handle side effects and external interactions
Domains
Domains are where you define your:
- State fields and their default values
- Events that can occur and how they modify state
- Commands that can be executed and their validation rules
Services
Services react to domain events and handle:
- External API calls
- Database operations
- Email sending
- Any other side effects
Basic Usage
Here's an example of building a simple AI chat application:
defmodule ChatApp.ConversationDomain do
use Sorcery.Domain,
domain do
# State fields
state :conversation, default: []
state :model, default: "gpt-4o"
state :system_prompt, default: "You are a helpful assistant."
state :token_count, default: 0
# Event definitions
event :user_message_received do
field :message, type: :string
apply fn state, event ->
%{state |
conversation: [%{content: event.message, role: :user} | state.conversation]
}
end
handle fn state, event ->
{:ok, [LLMPromptRequested{system_prompt: state.system_prompt, conversation: state.conversation}]}
end
end
event :llm_prompt_requested do
field :prompt, type: :string
end
event :model_changed do
field :model, type: :string
apply fn state, event ->
%{state | model: event.model}
end
end
event :llm_response_received do
field :response, type: :string
field :token_count, type: :integer
apply fn state, event ->
%{state |
conversation: [%{content: event.response, role: :assistant} | state.conversation],
token_count: state.token_count + event.token_count
}
end
end
command :change_model do
field :model, type: :string
handle fn state, command ->
if command.model in ["gpt-4o", "gpt-4o-mini"] do
{:ok, [ModelChanged{model: command.model}]}
else
{:error, :invalid_model}
end
end
# Command definition
command :send_message do
field :message, type: :string
handle fn state, command ->
{:ok, [UserMessageReceived{message: command.message},LLMPromptRequested{system_prompt: state.system_prompt, conversation: state.conversation}]}
end
end
end
end
endAnd a corresponding service to handle LLM interactions:
defmodule ChatApp.LLMService do
use Sorcery.Service
service do
on ChatApp.ConversationDomain.Events.LLMPromptRequested do
fn event ->
response = OpenAI.chat_completion(
model: event.model,
system: event.system_prompt,
messages: event.conversation
)
[%ChatApp.ConversationDomain.Events.LLMResponseReceived{
response: response,
token_count: response.usage.total_tokens
}]
end
end
end
endExecuting Commands
Commands can be executed easily:
Sorcery.execute(ConversationDomain, ConversationDomain.Commands.SendMessage{
message: "Hello, how are you?"
})The domain process will be automatically started if it doesn't exist.
Best Practices
- Keep domains focused on business logic
- Use services for all external interactions
- Design events to capture meaningful state changes
- Make commands validate their inputs
- Keep event handlers pure and predictable
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Documentation
Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/sorcery.