X3m.System

Build Status Hex version Coverage 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.3"}
]
end

One dependency is optional:

A minimal example

Define a router that registers a service and the module that handles it, and register the services (typically from your application's start/2).

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
:ok = MyApp.Router.register_services()

Dispatch a message to the service by name:

: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.

Dispatch accross the cluster

If you want to try dispatch accross the cluster, run 2 iex sessions:

iex --sname x3m_1@localhost -S mix

and from the other terminal

iex --sname x3m_2@localhost -S mix

Define your router and message handler (Greeter here) and register your services in first iex session (like in example from above), and then from the second one, connect to the first node:

Node.connect :"x3m_1@localhost"
#=> true

... and then dispatch message the same way you did in previous example. Result will be the same.

Guides

Example

The Banking example is a complete poncho project demonstrating the full CQRS/ES flow — HTTP API, command aggregates, event store, listener-driven read model, and cross-node dispatch. See its README for setup and curl walkthrough.

License

Released under the MIT License. See the LICENSE file.