X3m.System

Build StatusHex versionCoverage Status

Building blocks for distributed and/or CQRS/ES systems in Elixir.

X3m.System gives you a small set of composable pieces for building message-driven backends: a message that carries a request and its response, a router that registers services across a cluster, a dispatcher that finds a node offering a service and waits for the reply, and — when you need it — aggregates with event sourcing and a backend-agnostic scheduler for delivering messages in the future.

The pieces are à la carte. You can use the messaging layer (message + router + dispatcher) on its own, add aggregates and event sourcing only where you need them, and use the scheduler independently of everything else.

Installation

def deps do
[
{:x3m_system, "~> 0.9.1"}
]
end

One dependency is optional:

A minimal example

Define a router that registers a service and the module that handles it:

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

Register the services (typically from your application's start/2) and dispatch a message to the service by name:

:ok = MyApp.Router.register_services()
:greet
|> X3m.System.Message.new(raw_request: %{"name" => "Ada"})
|> X3m.System.Dispatcher.dispatch()
#=> %X3m.System.Message{response: {:ok, "Hello, Ada!"}, ...}
flowchart LR
C[Caller] -->|"Message.new(:greet)"| D[Dispatcher.dispatch]
D -->|find a node offering :greet| R[Router]
R -->|"authorize/1"| A{authorized?}
A -->|no| F["response: {:error, :forbidden}"]
A -->|yes| H["Greeter.greet/1"]
H -->|"{:reply, Message.ok(...)}"| C

No aggregates or event store are involved here — any module registered through a router can be a dispatch target.

Guides

License

Released under the MIT License. See the LICENSE file.