derekkraan / delta_crdt_ex

Use DeltaCrdt to build distributed applications in Elixir

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Proposal to read a single value from AWLWWMap

sascha-wolf opened this issue · comments

First things first: I've went through the source code and noticed that the AWLWWMap not only offers read/1 to get the whole map but also read/2 to a subset of keys.

I'd like to open a PR which uses read/2 to read a single value from the AWLWWMap, similar to Map.get/2. But from where I'm standing I'm under the impressions that the DeltaCrdt module has been explicitly designed to not assume any kind of particular data structure "under the hood", which means that a DeltaCrdt.get/2 would break that assumption.

As such I'd like to ask: how could an API look like which allows to use a "more sophisticated" read operation on the underlying data structure, such as read/2 on AWLWWMap?

What I imagine could work is something like this:

@timeout 5_000

#...

def read(crdt), do: read(crdt, [], @timeout)
def read(crdt, args) when is_list(args), do: read(crdt, args, @timeout)
def read(crdt, timeout), do: read(crdt, [], timeout)

def read(crdt, args, timeout) do
  GenServer.call(crdt, {:read, args}, timeout)
end

And adjusting the casual_crdt GenServer to accept a {:read, args} message. This in turn would keep the previous API intact - making for a non breaking change - but allow for the following API usage:

iex> DeltaCrdt.mutate(crdt, :add, ["my-key", "my-value"])
iex> DeltaCrdt.mutate(crdt, :add, ["another-key", "another-value"])
iex> DeltaCrdt.read(crdt, ["my-key"])
%{"my-key" => "my-value"}

Any thoughts on this?

Hi @sascha-wolf,

The library was originally designed to support any arbitrary delta_crdt, but over the course of time evolved to do just this one CRDT more effectively (including using MerkleMap for efficient propagation). So that's the reason for the odd layout of the internals.

I would accept a PR that adds a read/3. I think the following signatures make the most sense:

def read(crdt, args, timeout \\ 5000) when is_list(args)
def read(crdt, timeout \\ 5000)

I'm not sure about making [] synonymous with all keys, I think if you submit an empty list of keys we should short-circuit and return an empty list back.

Thanks for the super quick reply @derekkraan!

My train of thought on read(crdt, []) reading everything was to stay as close as possible to the semantics of mutate, that is, the arguments get used in an apply call.

So for read that would basically boil down to this code in casual_crdt.ex:

  def handle_call({:read, args}, _from, state) do
    {:reply, apply(state.crdt_module, :read, [state.crdt_state | args]), state}
  end

So read(crdt, []) would result in calling AWLWWMap.read(state) while read(crdt, ["foo"]) would call AWLWWMap.read(crdt, "foo"). If at some point something other than AWLWWMap comes along which accepts more than a single additional argument on read the DeltaCrdt API would be able to handle this.

I understand your train of thought, but at this point I'm not sure there will ever be anything other than AWLWWMap, so I'm perfectly ok with an API that more closely reflects the semantics of AWLWWMap. I am also planning to deprecate mutate at some point and add functions that are more reflective of a map in the future, to make the API a bit easier, since supporting multiple types of CRDT is no longer a design goal of this library.

Ah, I see. In that case I'll open a PR based on your suggestion. 🙂