Powered by AppSignal & Oban Pro

3. GenServer

3_genserver.livemd

3. GenServer

Simple key value storage using GenServer

GenServer is also kind of like Agent. The difference is that Agent only support a limit set of interaction (Agent.update, Agent.get, Agent.cast). Because of that it lacks the ability to do background tasks by itself.

GenServer is a more advanced form. It supports generic handlers like handle_cast, handle_info, handle_call (with pattern matching) and the ability to send a message directly to itself.

defmodule KeyValueStore do
  use GenServer

  # Public API
  def start(initial_state \\ %{}) do
    GenServer.start(__MODULE__, initial_state, name: __MODULE__)
  end

  def put(key, value) do
    GenServer.cast(__MODULE__, {:put, key, value})
  end

  def get(key) do
    GenServer.call(__MODULE__, {:get, key})
  end

  def delete(key) do
    GenServer.cast(__MODULE__, {:delete, key})
  end

  # GenServer Callbacks
  def init(initial_state) do
    {:ok, initial_state}
  end

  def handle_cast({:put, key, value}, state) do
    # Update the state with the new key-value pair
    new_state = Map.put(state, key, value)
    {:noreply, new_state}
  end

  def handle_cast({:delete, key}, state) do
    # Remove the key from the state
    new_state = Map.delete(state, key)
    {:noreply, new_state}
  end

  def handle_call({:get, key}, _from, state) do
    # Retrieve the value associated with the key or return :not_found
    value = Map.get(state, key, :not_found)
    {:reply, value, state}
  end
end

pid =
  case KeyValueStore.start() do
    {:ok, pid} -> pid
    {:error, {:already_started, pid}} -> pid
  end
KeyValueStore.put(:foo, "bar")
IO.puts("Retrieved foo = #{KeyValueStore.get(:foo)}")
KeyValueStore.delete(:foo)
Process.exit(pid, :kill)

Time checking GenServer

Example for the ability to work indepently.

defmodule DateTimeStore do
  use GenServer

  # Public API

  def start() do
    GenServer.start(__MODULE__, %{datetime: nil}, name: __MODULE__)
  end

  def get_datetime do
    GenServer.call(__MODULE__, :get_datetime)
  end

  # GenServer Callbacks
  def init(state) do
    # Set up a timer to send a message every 5000 ms (5 seconds)
    :timer.send_interval(5000, :check_datetime)
    {:ok, state}
  end

  def handle_info(:check_datetime, state) do
    # Fetch the current datetime and store it in the state
    current_datetime = DateTime.utc_now()
    {:noreply, %{state | datetime: current_datetime}}
  end

  def handle_call(:get_datetime, _from, %{datetime: nil} = state) do
    {:reply, :no_datetime_stored, state}
  end

  def handle_call(:get_datetime, _from, %{datetime: datetime} = state) do
    # Return the stored datetime
    {:reply, datetime, state}
  end
end

pid =
  case DateTimeStore.start() do
    {:ok, pid} -> pid
    {:error, {:already_started, pid}} -> pid
  end
IO.puts("Fetched datetime: #{DateTimeStore.get_datetime()}")
Process.exit(pid, :kill)