Packages
Multi-surface application runtime for Elixir. One TEA module renders to terminal, browser (LiveView), SSH, and MCP (agents). 30+ widgets, flexbox + CSS grid, AI agent runtime, distributed swarm with CRDTs, time-travel debugging, session recording, sandboxed REPL, and agentic commerce.
Current section
Files
Jump to
Current section
Files
lib/raxol/architecture/cqrs/command_dispatcher.ex
defmodule Raxol.Architecture.CQRS.CommandDispatcher do
@moduledoc """
Command dispatcher for CQRS pattern implementation.
Handles command routing, middleware processing, and handler management
in a functional programming style.
"""
use GenServer
alias Raxol.Core.Runtime.Log
defstruct [
:handlers,
:middleware,
:statistics
]
@type command :: any()
@type handler :: module()
@type middleware :: module()
@type statistics :: %{
commands_processed: non_neg_integer(),
commands_failed: non_neg_integer(),
handlers_registered: non_neg_integer(),
middleware_count: non_neg_integer()
}
# Client API
@doc """
Starts the command dispatcher.
"""
def start_link(opts \\ []) do
name = Keyword.get(opts, :name, __MODULE__)
GenServer.start_link(__MODULE__, [], name: name)
end
@doc """
Add middleware to the dispatcher.
"""
def add_middleware(middleware) when is_atom(middleware) do
GenServer.call(__MODULE__, {:add_middleware, middleware})
end
@doc """
Register a command handler.
"""
def register_handler(command, handler)
when is_atom(command) and is_atom(handler) do
GenServer.call(__MODULE__, {:register_handler, command, handler})
end
@doc """
Dispatch a command through the middleware chain to its handler.
"""
def dispatch(command) do
GenServer.call(__MODULE__, {:dispatch, command})
end
@doc """
List all registered handlers.
"""
def list_handlers do
GenServer.call(__MODULE__, :list_handlers)
end
@doc """
Get dispatcher statistics.
"""
def get_statistics do
GenServer.call(__MODULE__, :get_statistics)
end
@doc """
Remove a handler for a command.
"""
def unregister_handler(command) when is_atom(command) do
GenServer.call(__MODULE__, {:unregister_handler, command})
end
@doc """
Remove middleware from the dispatcher.
"""
def remove_middleware(middleware) when is_atom(middleware) do
GenServer.call(__MODULE__, {:remove_middleware, middleware})
end
# Server callbacks
@impl true
def init([]) do
state = %__MODULE__{
handlers: %{},
middleware: [],
statistics: %{
commands_processed: 0,
commands_failed: 0,
handlers_registered: 0,
middleware_count: 0
}
}
{:ok, state}
end
@impl true
def handle_call({:add_middleware, middleware}, _from, state) do
case validate_middleware(middleware) do
:ok ->
new_middleware = [middleware | state.middleware]
new_stats = Map.update!(state.statistics, :middleware_count, &(&1 + 1))
new_state = %{state | middleware: new_middleware, statistics: new_stats}
{:reply, :ok, new_state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
@impl true
def handle_call({:register_handler, command, handler}, _from, state) do
case validate_handler(handler) do
:ok ->
new_handlers = Map.put(state.handlers, command, handler)
new_stats =
Map.update!(state.statistics, :handlers_registered, &(&1 + 1))
new_state = %{state | handlers: new_handlers, statistics: new_stats}
{:reply, :ok, new_state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
@impl true
def handle_call({:dispatch, command}, _from, state) do
command_type = get_command_type(command)
case Map.get(state.handlers, command_type) do
nil ->
new_stats = Map.update!(state.statistics, :commands_failed, &(&1 + 1))
new_state = %{state | statistics: new_stats}
{:reply, {:error, :handler_not_found}, new_state}
handler ->
case execute_command_pipeline(command, handler, state.middleware) do
{:ok, result} ->
new_stats =
Map.update!(state.statistics, :commands_processed, &(&1 + 1))
new_state = %{state | statistics: new_stats}
{:reply, {:ok, result}, new_state}
{:error, reason} ->
new_stats =
Map.update!(state.statistics, :commands_failed, &(&1 + 1))
new_state = %{state | statistics: new_stats}
{:reply, {:error, reason}, new_state}
end
end
end
@impl true
def handle_call(:list_handlers, _from, state) do
handlers_list =
Enum.map(state.handlers, fn {command, handler} ->
%{command: command, handler: handler}
end)
{:reply, handlers_list, state}
end
@impl true
def handle_call(:get_statistics, _from, state) do
{:reply, state.statistics, state}
end
@impl true
def handle_call({:unregister_handler, command}, _from, state) do
new_handlers = Map.delete(state.handlers, command)
new_stats =
Map.update!(state.statistics, :handlers_registered, &max(0, &1 - 1))
new_state = %{state | handlers: new_handlers, statistics: new_stats}
{:reply, :ok, new_state}
end
@impl true
def handle_call({:remove_middleware, middleware}, _from, state) do
new_middleware = Enum.reject(state.middleware, &(&1 == middleware))
new_stats =
Map.update!(state.statistics, :middleware_count, &max(0, &1 - 1))
new_state = %{state | middleware: new_middleware, statistics: new_stats}
{:reply, :ok, new_state}
end
# Private functions
defp get_command_type(command) when is_atom(command), do: command
defp get_command_type(command) when is_struct(command), do: command.__struct__
defp get_command_type(%{__type__: type}), do: type
defp get_command_type(_), do: :unknown_command
defp execute_command_pipeline(command, handler, middleware) do
middleware
|> Enum.reverse()
|> Enum.reduce(
fn -> execute_handler(handler, command) end,
fn current_middleware, next_fn ->
fn -> apply_middleware(current_middleware, command, next_fn) end
end
)
|> apply([])
rescue
error ->
Log.error("Command execution failed: #{inspect(error)}")
{:error, {:execution_failed, error}}
end
defp apply_middleware(middleware, command, next_fn) do
case function_exported?(middleware, :call, 2) do
true ->
middleware.call(command, next_fn)
false ->
Log.warning(
"Middleware #{middleware} does not implement call/2, skipping"
)
next_fn.()
end
rescue
error ->
Log.error("Middleware #{middleware} failed: #{inspect(error)}")
{:error, {:middleware_failed, middleware, error}}
end
defp execute_handler(handler, command) do
case function_exported?(handler, :handle, 1) do
true ->
handler.handle(command)
false ->
case function_exported?(handler, :call, 1) do
true ->
handler.call(command)
false ->
Log.error(
"Handler #{handler} does not implement handle/1 or call/1"
)
{:error, {:invalid_handler, handler}}
end
end
rescue
error ->
Log.error("Handler #{handler} failed: #{inspect(error)}")
{:error, {:handler_failed, handler, error}}
end
defp validate_middleware(middleware) do
cond do
not is_atom(middleware) ->
{:error, :middleware_must_be_atom}
not function_exported?(middleware, :call, 2) ->
{:error, :middleware_must_implement_call_2}
true ->
:ok
end
end
defp validate_handler(handler) do
cond do
not is_atom(handler) ->
{:error, :handler_must_be_atom}
not (function_exported?(handler, :handle, 1) or
function_exported?(handler, :call, 1)) ->
{:error, :handler_must_implement_handle_1_or_call_1}
true ->
:ok
end
end
end