Packages

A library for implementing distributed generic replicated state machines using the Raft consensus algorithm

Current section

Files

Jump to
graft lib graft.ex
Raw

lib/graft.ex

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.
"""
@doc """
Starts the raft cluster.
`servers` - list of server names.
`machine_module` - module using the `Graft.Machine` behaviour to define the replicated state machine.
`machine_args` - an optional list of arguments that are passed to the `init` function of the machine.
Returns `{:ok, pid}` where `pid` is the supervisor pid which can be used to supervise the cluster's servers.
"""
@spec start(list(atom), module(), list(any)) :: {:ok, pid()}
def start(servers, machine_module, machine_args \\ []) do
{:ok, supervisor_pid} = Graft.Supervisor.start_link servers, machine_module, machine_args
for server <- servers, do: Supervisor.start_child supervisor_pid, [server, servers, machine_module, machine_args]
for server <- servers, do: GenStateMachine.cast server, :start
{:ok, supervisor_pid}
end
@doc """
Print out the internal state of the `server`.
"""
def data(server), do: GenStateMachine.call(server, :data)
@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