derekkraan / delta_crdt_ex

Use DeltaCrdt to build distributed applications in Elixir
MIT License
493 stars 36 forks source link

Support for automatic cluster formation #62

Open ckreiling opened 3 years ago

ckreiling commented 3 years ago

Horde has the helpful NodeListener genserver that I've been copy-pasting into toy projects where I'm playing with DeltaCrdt. My approach to libraries built on top of DeltaCrdt has been to start the DeltaCrdt process under a supervisor, with a downstream child that bootstraps node membership. It'd be nice to have an optional NodeListener baked into DeltaCrdt - or even a setup like mine that use a supervisor.

Here's a ReplicatedMap implementation I came up with:

defmodule ReplicatedMap do
  use Supervisor

  alias ReplicatedMap.{NodeWatcher}

  def start_link(opts) do
    opts = Keyword.put_new(opts, :name, __MODULE__)
    Supervisor.start_link(__MODULE__, opts, name: :"#{opts[:name]}.Supervisor")
  end

  def init(opts) do
    name = opts[:name]
    members = opts[:members] || :auto

    children = [
      {DeltaCrdt, [name: name, crdt: DeltaCrdt.AWLWWMap]},
      with_members(members, name)
    ]

    # Restart the node watcher when the ReplicatedMap crashes
    # so that node membership is properly resync'd w/ the DeltaCrdt.
    Supervisor.init(children, strategy: :rest_for_one)
  end

  @spec set_members(map :: module(), members :: [GenServer.server()])
  def set_members(map, members) when is_list(members) do
    DeltaCrdt.set_neighbours(map, members)
  end

  @spec put(map :: module(), key :: any(), value :: any()) :: module()
  def put(map \\ __MODULE__, key, value) do
    DeltaCrdt.put(map, key, value)
  end

  @spec delete(map :: module(), key :: any()) :: module()
  def delete(map \\ __MODULE__, key) do
    DeltaCrdt.delete(map, key)
  end

  @spec drop(map :: module(), keys :: [any()]) :: module()
  def drop(map \\ __MODULE__, keys) when is_list(keys) do
    DeltaCrdt.drop(map, keys)
  end

  @spec get(map :: module(), key :: any()) :: nil | any()
  def get(map \\ __MODULE__, key) do
    case DeltaCrdt.read(map, [key]) do
      %{^key => val} -> val
      _ -> nil
    end
  end

  defp with_members(:auto, crdt_name) do
    {NodeWatcher, [name: :"#{crdt_name}.NodeWatcher", crdt_name: crdt_name]}
  end

  defp with_members(members, crdt_name) when is_list(members) do
    {Task, fn -> DeltaCrdt.set_neighbours(crdt_name, members) end}
  end
end

This library has been so fun to play with, and I use it to show off "the power of Elixir/Erlang" whenever I have the chance.