Packages
ex_sorcery
0.1.0
A toolkit for building event-driven applications in Elixir with event sourcing and CQRS patterns
Current section
Files
Jump to
Current section
Files
lib/sorcery/service.ex
defmodule Sorcery.Service do
@moduledoc """
Defines a service that can handle events from domains.
Services are used to handle side effects (like sending emails)
or to maintain read models (views).
## Auto-Starting
Services can be configured to auto-start when your application boots.
This ensures that no events are missed and read models stay consistent.
Configure auto-starting in your config:
config :sorcery, :auto_start,
services: [
MyApp.NotificationService,
MyApp.ReadModelService
]
Services not configured for auto-start must be started manually.
## Example
defmodule MyApp.NotificationService do
use Sorcery.Service
service do
on UserRegistered do
send_welcome_email(event)
end
on OrderPlaced do
send_order_confirmation(event)
end
end
defp send_welcome_email(event) do
# Send email logic
end
defp send_order_confirmation(event) do
# Send confirmation logic
end
end
"""
defmacro __using__(_opts) do
quote do
import Sorcery.Service, only: [service: 1]
Module.register_attribute(__MODULE__, :event_handlers, accumulate: true)
@before_compile Sorcery.Service
end
end
defmacro service(do: block) do
quote do
import Sorcery.Service
unquote(block)
end
end
defmacro on(event_module, do: handler) do
quote do
handler_fn = unquote(Macro.escape(handler))
Module.put_attribute(__MODULE__, :event_handlers, {unquote(event_module), handler_fn})
end
end
defmacro __before_compile__(env) do
event_handlers = Module.get_attribute(env.module, :event_handlers)
handle_event_clauses =
for {event_module, handler} <- event_handlers do
quote do
def handle_event(%unquote(event_module){} = event) do
unquote(handler).(event)
end
end
end
default_handle_event =
quote do
def handle_event(_), do: []
end
quote do
use GenServer
def start_link(opts \\ []) do
name = Keyword.get(opts, :name, __MODULE__)
GenServer.start_link(__MODULE__, [], name: name)
end
def init(_) do
# Subscribe to the event store
Sorcery.EventStore.subscribe()
{:ok, %{}}
end
def handle_info({:event, event}, state) do
# Convert the event data back to a struct
domain_event = Sorcery.EventConverter.event_store_to_domain(event)
# Handle the event
service_events = handle_event(domain_event)
unless service_events == [] do
service_events
|> Enum.map(&Sorcery.EventConverter.domain_to_event_store(&1, event.metadata.domain, event.metadata.instance_id))
|> Sorcery.EventStore.append()
end
{:noreply, state}
end
unquote_splicing(handle_event_clauses)
unquote(default_handle_event)
end
end
end