Bedrock - a scaleable, distributed key-value database with better-than-ACID guarantees

…and here’s what it would look like if everything were implicit, like ecto:

defmodule Scheduling do
  @total_seats_available 100

  def signup(attends, course, student, class) do
    Repo.transact(fn ->
      rec = Subspace.pack(attends, {student, class})

      case Repo.get(rec) do
        nil ->
          # Not signed up yet, proceed with signup
          class_key = Subspace.pack(course, class)
          seats_data = Repo.get(class_key)
          seats_left = Key.unpack(seats_data)

          if seats_left == 0 do
            {:error, "No remaining seats"}
          else
            # Decrement seats and record signup
            Repo.put(class_key, Key.pack(seats_left - 1))
            Repo.put(rec, <<>>)
          end

        _existing ->
          # Already signed up
          :ok
      end
    end)
  end

  def drop(attends, course, student, class) do
    Repo.transact(fn ->
      rec = Subspace.pack(attends, {student, class})

      case Repo.get(rec) do
        nil ->
          # Not taking this class
          :ok

        _existing ->
          # Increment seats and remove signup
          class_key = Subspace.pack(course, class)
          seats_data = Repo.get(class_key)
          seats_left = Key.unpack(seats_data)

          Repo.put(class_key, Key.pack(seats_left + 1))
          Repo.clear(rec)
      end
    end)
  end

  def available_classes(course) do
    course_range = Subspace.range(course)

    Repo.transact(fn ->
      classes = Repo.get_range(course_range)
        |> Stream.map(fn {packed_class, packed_seats} ->
          class = Subspace.unpack(course, packed_class)
          availability = Key.unpack(packed_seats)
          {class, availability}
        end)
        |> Stream.filter(fn {_class, availability} -> availability > 0 end)
        |> Stream.map(fn {class, _availability} -> class end)
        |> Enum.to_list()
      {:ok, classes}
    end)
  end

  def init(scheduling, course, class_names) do
    scheduling_range = scheduling |> Directory.get_subspace() |> Subspace.range()

    Repo.transact(fn ->
      # Clear the directory
      Repo.clear_range(scheduling_range)

      # Add all classes
      for class_name <- class_names do
        add_class(course, class_name)
      end

      :ok
    end)
  end

  def add_class(course, class) do
    Repo.transact(fn ->
      key = Subspace.pack(course, class)
      value = Key.pack(@total_seats_available)
      Repo.put(key, value)
    end)
  end
end

…and…

def switch(attends, course, student, old_class, new_class) do
  Repo.transact(fn ->
    with :ok <- signup(attends, course, student, new_class),
         :ok <- drop(attends, course, student, old_class) do
      {:ok, :switched}
    end
  end)
end

…other than the Key.pack for values naming pinchpoint that @jstimps called out, that’s all of of the feedback integrated. If any of the implicit functions are used outside of a transact, they’ll raise.

1 Like