GenServer, a simple way to work with Elixir process.
Tags: genserver, elixir, supervisor
Published: 2024-06-26 09:56:21

In this [topic](https://dev.to/manhvanvu/elixir-process-what-is-how-work-bgj) I have talk about Elixir process now I talk about `GenServer` & use cases in Elixir.

`GenServer` is a template/skeleton for work with process like a server/client model. It's easy to add to `Supervisor` and easy to build a robust system (a super weapon in Elixir/Erlang world).

GenServer included 2 parts, one is server side it's included in language, an other part is client side.

_Of course, we can self make our `GenServer` but with existed `GenServer` we have a lot of benefits like: handle errors, support `Supervisor`, don't need effort to wrap/handle message for communicating between two processes._

Flow of `GenServer`:

Other processes <--> Our public APIs <--> GenServer client <-send request(msg) - [wait result]-> GenServer server loop <--> Our GenServer callbacks

Function call flow of `GenServer`:

![function call flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bf1dy54nthv9qwmzi8kz.jpg)
(result can be missed for handle_cast or return `:noreply` from our callback functions)

`GenServer` can handle a lot of requests from other processes and ensure only one event is processing at a time. We can use `GenServer` for sharing state/global data and avoid race condition (by atomic execution - a request is complete action/transaction).

`GenServer` handle events by pass messages between our client code and callback api of `GenServer`, downside of this is reduce performance & message queue can raise an OMM error, you need to take care this if you develop a large scale system.

**Server part**

This part have included code for handle errors, support `Supervisor`, hot reload code, terminating and our implemented callback APIs.

Server side (a process) maintains state (a term - map, tuple, list,...) for us. We get/update state by implement callbacks of `GenServer`.

To start a `GenServer` we need to call public api `start_link` (common & official way) (by add to a supervisor, directly call in runtime & Elixir shell) then `GenServer` will call our init callback to init state. 

A thing to remember in start phase is name (option: `:name`) of `GenServer` it uses to call from other processes (or we use returned pid, less convenience). Name of `GenServer` can set to local (call only in local node), global (register in all nodes in cluster, can call remotely from other node) or a mechanism like `Registry` for managing.

After init, `GenServer` go to loop function for handle events (wait messages that are wrapped in tuple with special formats for detecting kind of event) from client side (other process).

For implement server events, `GenServer` provided some groups of callback functions:
- handle_call, for client send request & get result.
- handle_cast, for client send request & doesn't need result.
- handle_info, for direct message was sent to server by send function.
- other functions like: init (for init state), terminate (shutdown `GenServer`, code_change (for hot reload code, I will go to this in other post).

Notice: Our state (data) on `GenServer` can rescue when `GenServer` is crashed. That is a one of technics using to build a robust system.

We have group of functions need to implement for server side:
- Callback functions, for code in server calling.
- `start_link` (common way, for easy integrate to a supervisor) function call to `GenServer.start_link`.

**Client part**

This part is our implement code for public APIs for other process (client) can send request to `GenServer` & `start_link` (for easy integrate to a supervisor) function (if needed).

Normally, we have group of functions:
- Public functions, for our code (process) can call to get/update state on server and return result (if needed).

**Step init & handle event from server code**

Almost case, `GenServer` started from `Supervisor` by call `start_link` (a common name) or other public function with params (or not) then call to `GenServer.start_link` like:

```Elixir 
GenServer.start_link(__MODULE__, nil, name: __MODULE__)
```

After that `GenServer` will call `init` callback function for init our state then `GenServer` will help us maintain our state.

init function example:

```Elixir
  @impl true
  def init(_) do
    # init state.
    state = %{}

    {:ok, state}
  end
```
(code from my team, create an empty map and return it as state to `GenServer`) 

Now in our `GenServer` has state(data) for get/update we need implement hanlde_call or handle_cast callback and add a simple public function to call our request

example:

```Elixir
  # Public function
  def add_stock({_stock, _price} = data) do
    GenServer.cast(__MODULE__, {:add_stock, data})
  end
  
  # callback function
  @impl true
  def handle_cast( {:add_stock, {stock, price}, state) do
    {:noreply, Map.put(state, stock, price)}
  end
```
(this code implement public api and callback to add a stock and price to state (a map))

```Elixir
  # Public function
  def get_stock(stock) do
    GenServer.call(__MODULE__, {:get_stock, stock})
  end
  
  # callback function
  @impl true
  def handle_call({:get_stock, stock}, _from, state) do
    {:reply, Map.get(state, stock), state}
  end
```
(this couple of function is wrap a public api for easy call from outside, a callback function for get data from state)

We can use pattern matching in the header of callback function or move it to a private function if needed.

call/cast event is main way to communicate with `GenServer` but we can an other way is `handle_info` for send request to `GenServer`.

Example:
```Elixir
  # callback function
  @impl true
  def handle_info({:get_stock, from, stock}, state) do
    send(from, Map.get(state, stock))

    {:noreply, state}
  end

```
(this code handle directly request came from other process (or itself) by `send(server_pid, {:get_stock, self(), "Stock_A"))`)

For every event (call, cast, handle_info) we can send other message to `GenServer` to tell stop, error. Please check it more on [hexdocs](https://hexdocs.pm/elixir/GenServer.html)

hot_reload code, for case we need update state (example: change data format in our state) we can implement code_change to do that.

 Example:
```Elixir
  # callback
  @impl true
  def code_change(_old_vsn, state, _extra) do
    ets = create_outside_ets()
    put_old_state_to_ets(state)
    {:ok, state}
  end
```
(this code will handle case we update our `GenServer` - convert data from map to `:ets` table).

After all, if need to clean up state when `GenServer` we can implement `terminate` callback.

Example:
```Elixir
  # callback
  @impl true
  def terminate(reason, state) do
    clean_up_outside_ets()

    :normal
  end
```
(this code help us clean up state (data) if it use outside resource).

**Using GenServer with Supervisor**

Very convenience for use if use `GenSever` with `Supervisor`. We can add to application supervisor or our `DynamicSupervisor`.

Example we have  `GenServer` declare a meta & init + start_link like:

```Elixir
defmodule Demo.StockHolder do
  use GenServer, restart: :transient

  def start_link(_) do
    GenServer.start_link(__MODULE__, :start_time, name: __MODULE__)
  end

  # ...
```
(See a keyword `use`, we can add meta data for init a child for `Supervisor`, in here i add `:restart` strategy only, other can check on docs)

Now we can add directy to application supervisor like:
```Elixir
defmodule Demo.Application do
  use Application

  @impl true
  def start(_type, _args) do
    children = [
      Demo.StockHolder
    ]

    opts = [strategy: :one_for_one, name: Demo.Supervisor]
    Supervisor.start_link(children, opts)
  end
```

Now we have a `GenServer` start follow our application.

For case using with `DynamicSupervisor` we add a `Supervisor` to our application like:
```Elixir
defmodule Demo.Application do
  use Application

  @impl true
  def start(_type, _args) do
    children = [
       {DynamicSupervisor, name: Demo.DynamicSupervisor, strategy: :one_for_one}
    ]

    opts = [strategy: :one_for_one, name: Demo.Supervisor]
    Supervisor.start_link(children, opts)
  end
```

and in our code we can add our `GenServer` like:
```Elixir
DynamicSupervisor.start_child(Demo.DynamicSupervisor, Demo.StockHolder)
```

**Use cases for GenServer**

1. Sharing data between process.
2. Handle errors & make a robust system.
3. Easy make a worker process for `Supervisor`.
4. Easy to add code support for hot reload code(very interesting feature).

Now we can easy work with `GenServer` and save a lot of time for work with process/supervisor.