X3m.System is a set of building blocks for message-driven Elixir backends. This
guide gets you from an empty project to dispatching your first message. It uses only
the messaging layer — no aggregates or event store required.
Install
Add the dependency:
def deps do
[
{:x3m_system, "~> 0.9"}
]
endOne dependency is optional:
:elixir_uuid— id generation when working with aggregates.
X3m.System starts its own OTP application (a task supervisor, a node monitor and the
service registry), so once it is a dependency there is nothing to add to your
supervision tree to use the messaging layer.
The three core pieces
X3m.System.Message— the struct that flows through the system. It carries the service name, the raw request, sharedassigns, and — once handled — aresponse.X3m.System.Router— registers services (named entry points) and maps each to a module/function that handles it.X3m.System.Dispatcher— given a message, finds a node offering its service, invokes it, and returns the message with itsresponseset.
Your first service
Define a router and a handler module:
defmodule MyApp.Router do
use X3m.System.Router
service :greet, MyApp.Greeter
def authorize(_message), do: :ok
end
defmodule MyApp.Greeter do
alias X3m.System.Message
def greet(%Message{} = message) do
name = message.raw_request["name"]
{:reply, Message.ok(message, "Hello, #{name}!")}
end
endA few things to note:
service :greet, MyApp.Greeterregisters the:greetservice and routes it toMyApp.Greeter.greet/1(same name). Use the three-argument formservice :greet, MyApp.Greeter, :handle_greetto call a differently named function.- A handler returns
{:reply, message}to send the message back to the caller, or:noreplyto stay silent. authorize/1runs before the handler. Returning:oklets the call through; anything else becomes the response. The default (if you don't define it) denies everything, so you always define it explicitly.
Register and dispatch
Services are announced to the registry at runtime — usually from your application's
start/2:
:ok = MyApp.Router.register_services()Then build a message and dispatch it:
:greet
|> X3m.System.Message.new(raw_request: %{"name" => "Ada"})
|> X3m.System.Dispatcher.dispatch()
#=> %X3m.System.Message{response: {:ok, "Hello, Ada!"}, ...}dispatch/2 blocks until the handler replies (default timeout 5s) and returns the
resolved message. Pattern-match on its response — see X3m.System.Response for the
full vocabulary of response shapes.
Where to go next
- Messaging — the full message lifecycle, routers and responses.
- Aggregates & event sourcing — model state with events.
- Distribution — run services across a cluster.
- Scheduling — deliver messages in the future.