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"}
  ]
end

One 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, shared assigns, and — once handled — a response.
  • 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 its response set.

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
end

A few things to note:

  • service :greet, MyApp.Greeter registers the :greet service and routes it to MyApp.Greeter.greet/1 (same name). Use the three-argument form service :greet, MyApp.Greeter, :handle_greet to call a differently named function.
  • A handler returns {:reply, message} to send the message back to the caller, or :noreply to stay silent.
  • authorize/1 runs before the handler. Returning :ok lets 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