defmodule Graft do @moduledoc """ An API of the raft consensus algorithm, allowing for custom client requests and custom replicated state machines. ## Example Let's create a distributed stack. The first step is to set up the state machine. Here we will use the `Graft.Machine` behaviour. ``` defmodule MyStackMachine do use Graft.Machine @impl Graft.Machine def init([]) do {:ok, []} end @impl Graft.Machine def handle_entry({:put, value}, state) do {:ok, [value | state]} end def handle_entry(:pop, []) do {:noop, []} end def handle_entry(:pop, [response | state]) do {response, state} end def handle_entry(_, state) do {:invalid_request, state} end end ``` Now that we have our state machine, we can define the servers that will make up the raft cluster. Each server must have a unique name. ``` servers = [:server1, :server2, :server3] ``` With both the servers and state machine, we can now run the graft funtion, which will start the servers and the consensus algorithm. ``` {:ok, supervisor} = Graft.start servers, MyStackMachine ``` `Graft.start` returns the supervisor pid from which we can terminate or restart the servers. We can now use `Graft.request` to make requests to our consensus cluster. As long as we know at least one server, we can send requests, since the `Graft.Client` module will forward the request if the server we choose is not the current leader. ``` Graft.request :server1, :pop #=> :noop Graft.request :server1, {:put, :foo} #=> :ok Graft.request :server1, :pop #=> :foo Graft.request :server1, :bar #=> :invalid_request ``` That completes the distributed stack. """ use Application def start(), do: for( server <- Application.fetch_env!(:graft, :cluster), do: GenStateMachine.cast(server, :start) ) def start(_type, _args), do: Graft.Supervisor.start_link() def stop(), do: Supervisor.stop(Graft.Supervisor) def leader(server), do: GenStateMachine.call(server, :leader) def stop_server(server), do: Supervisor.terminate_child(Graft.Supervisor, server) def restart_server(server), do: Supervisor.restart_child(Graft.Supervisor, server) @doc """ Print out the internal state of the `server`. """ def data(server), do: :sys.get_state(server) @doc """ Make a new client request to a server within the consensus cluster. `server` - name of the server the request should be sent to. `entry` - processed and applied by the replicated state machine. """ @spec request(atom(), any()) :: response :: any() def request(server, entry), do: Graft.Client.request(server, entry) end