Mix.install([
  {:dgen, path: "../dgen"},
  {:erlfdb, "~> 0.3"}
])

Regular GenServer

A simple chat server that stores messages in memory.

defmodule Message do
  defstruct [:id, :name, :text]
end

defmodule Chat do
  use GenServer

  defstruct idx: 0, messages: []

  def start(), do: GenServer.start(__MODULE__, [])

  def restart(pid) do
    GenServer.stop(pid)
    start()
  end

  def new_message(pid, name, text), do: GenServer.call(pid, {:new_message, name, text})

  def history(pid), do: GenServer.call(pid, :history)

  @impl true
  def init([]) do
    {:ok, %Chat{}}
  end

  @impl true
  def handle_call({:new_message, name, text}, _from, state=%Chat{}) do
    %{messages: messages, idx: idx} = state
    message = %Message{id: "#{idx+1}", name: name, text: text}
    resp_text = "Thanks for the message"
    response = %Message{id: "#{idx+2}", name: "system", text: resp_text}
    {:reply, resp_text, %{state | idx: idx+2, messages: messages ++ [message, response]}}
  end

  @impl true
  def handle_call(:history, _from, state=%Chat{}) do
    %{messages: messages} = state
    {:reply, messages, state}
  end
end
{:ok, pid} = Chat.start()

Send a message and get a response.

Chat.new_message(pid, "Alice", "Hello there")
Chat.history(pid)

Now restart the server.

{:ok, pid} = Chat.restart(pid)

The history is lost — regular GenServers don't persist state.

Chat.history(pid)

Durable GenServer

The same chat server, but using DGen.Server for automatic persistence.

defmodule DChat do
  use DGen.Server

  defstruct idx: 0, messages: []

  def start(tenant), do: DGen.Server.start(__MODULE__, [], [tenant: tenant])

  def restart(pid, tenant) do
    GenServer.stop(pid)
    start(tenant)
  end

  def new_message(pid, name, text), do: DGen.Server.call(pid, {:new_message, name, text})

  def history(pid), do: DGen.Server.call(pid, :history)

  @impl true
  def init([]) do
    {:ok, %DChat{}}
  end

  @impl true
  def handle_call({:new_message, name, text}, _from, state=%DChat{}) do
    %{messages: messages, idx: idx} = state
    message = %Message{id: "#{idx+1}", name: name, text: text}
    resp_text = "Thanks for the message"
    response = %Message{id: "#{idx+2}", name: "system", text: resp_text}
    {:reply, resp_text, %{state | idx: idx+2, messages: messages ++ [message, response]}}
  end

  @impl true
  def handle_call(:history, _from, state=%DChat{}) do
    %{messages: messages} = state
    {:reply, messages, state}
  end
end
b = :dgen_config.backend()
tenant = b.sandbox_open("DGen.Livebook", "Intro")

Create a tenant to isolate this server's data.

{:ok, pid} = DChat.start(tenant)
DChat.new_message(pid, "Bob", "Greetings")
DChat.history(pid)

Restart the durable server.

{:ok, pid} = DChat.restart(pid, tenant)

The history persists — DGen.Server automatically saves and restores state.

DChat.history(pid)