What is a GenServer?

GenServer (Generic Server) is one of the most common tools in the Elixir toolbag. I’ve written in the past about why you don’t need to know about GenServers to get started with Elixir. However, you will inevitably hit the point where you want (or need) them. This post will be my description of a GenServer to beginners to the language.

Data is the foundation of programming

A seemingly simple question: what is programming? At the end of the day, most programming is assigning data to variables, operating on that data, moving it through different layers that apply other transformations, and then exposing it to some type of interface.

Of course, we use libraries and frameworks for creating those interfaces (HTTP servers, Rails, Phoenix, etc.) although it’s turtles all the way down. We assign data to variables and operate on the variables.

This takes us to the concept of an Object in object-oriented programming languages. An object combines (encapsulates) data and behavior. Encapsulation is good, because we can define what we want to do with our data (imposing limitations or processes around operations) with the thing that holds our data. For example, here’s a Ruby object that stores arbitrary data:

class MyKeyStore
  def initialize
    @store = {}
  end

  def put(k, v)
    @store[k] = v
  end

  def get(k)
    @store[k]
  end
end

irb(main):046:0> store = MyKeyStore.new
=> #<MyKeyStore:0x0000000158209d20 @store={}>
irb(main):047:0> store.get("a")
=> nil
irb(main):048:0> store.put("a", "value")
=> "value"
irb(main):049:0> store.get("a")
=> "value"

“Okay, that’s Ruby. GenServers are Elixir.” Let’s look at how GenServers play into this.

GenServer Basics

A GenServer holds data and provides ways of interacting with that data. You can store any data you want in a GenServer (just like an Object in an OO language). You send messages to the GenServer’s process in order to interact with it. Ruby also uses message-passing to interact with objects, although is slightly different in practice.

Let’s see our key-value object turned into a GenServer:

defmodule MyKeyStore do
  def init([]) do
    # The map (%{}) here is our GenServer's state, it could
    # be *anything* we want it to be
    {:ok, %{}}
  end

  def handle_call({:put, k, v}, _from, state) do
    next_state = Map.put(state, k, v)
    # We control the return value (2nd argument) and the next
    # state of our GenServer (3rd argument)
    {:reply, v, next_state}
  end

  def handle_call({:get, k}, _from, state) do
    {:reply, Map.get(state, k), state}
  end
end

iex(3)> {:ok, pid} = GenServer.start(MyKeyStore, [])
{:ok, #PID<0.129.0>}
iex(4)> GenServer.call(pid, {:get, "a"})
nil
iex(5)> GenServer.call(pid, {:put, "a", "value"})
"value"
iex(6)> GenServer.call(pid, {:get, "a"})
"value"

At its core, this is all that a GenServer is. It holds data and lets you define ways of interacting with that data. This particular example feels a lot like an Object, although it’s important to note that programming Elixir like an object-oriented language is not recommended.

GenServers are capable of much more than this very basic example. This post isn’t serving to highlight what they can do, but just the basics of them. However, there’s one foundational element to GenServers that we absolutely need to talk about.

Data Lifecycle

Our programs are just data and operations on data, but there’s a crucial element that is missing: time. Every program that we write runs over a period of time. Often, we want to do things based on a certain frequency.

The object in our Ruby example lacks the ability to deal with time. We would need to build that ourselves and deal with all of the challenges that would be introduced. However, we can easily create a GenServer that changes over time:

defmodule ChangingNumber do
  def init([]) do
    send(self(), :change)
    # Our state this time is just a number, not a map like previous example
    {:ok, 0}
  end

  # handle_info is different than handle_call
  # No one is waiting for a response to this message, so we set the
  # new state and move on without replying.
  def handle_info(:change, _previous_number) do
    # Every 100ms, we will receive another change message
    Process.send_after(self(), :change, 100)
    next_number = :rand.uniform(9999)
    {:noreply, next_number}
  end

  def handle_call(:get, _from, number) do
    {:reply, number, number}
  end
end

iex(12)> {:ok, pid} = GenServer.start(ChangingNumber, [])
{:ok, #PID<0.156.0>}
iex(13)> GenServer.call(pid, :get)
4367
iex(14)> GenServer.call(pid, :get)
1357
iex(15)> GenServer.call(pid, :get)
8204

In this example, our ChangingNumber GenServer process is actually sending a message to itself in the future. Elixir’s runtime (the BEAM) dispatches this message to the process at the appropriate time.

This is a very simple example, but the ability to control data over time is extremely powerful. We can build a key-value cache that clears out old keys to ensure our data footprint stays small. We can continually refresh a piece of data from our database and keep that data in memory for quick access. Things that we would usually introduce a third-party dependency for, we can just do ourselves.

When I’m working with GenServers, I often think of them as a living thing. They can change over time (in the constraints that I’ve coded for them). They can emit messages to other GenServers in my system. They can be entirely contained or very sociable. They can exist for the duration of a web request, a WebSocket connection, or even exist as long as my system is up. I find that thinking of them in this way lets me build things that would be considered complex, but are very intuitive to develop and reason about.

Okay, last thing about GenServers. Let’s look at the concept of data independence.

Data Independence

If data is at the core of our systems, then data is important. We need to protect our data and make our data lifecycle easy to reason about for the engineers on our team.

GenServers live in their own little (mostly) isolated worlds. The technical reason for this is that they don’t share the same stack, and errors in one won’t propagate into an unrelated GenServer. This means that when we are thinking about MyKeyStore, we only need to think about it and how it exposes its data via messages. We don’t need to worry about how other pieces of the system might blow up and take our GenServer down.

This independence is why we don’t really need to think about explicit multi-threading in the Elixir world. Every GenServer is independent from others, and so our programs will execute on all available CPU cores without us needing to do anything at all.

For me, this is one of the big selling points of Elixir and GenServers. A good example of this is that I once wanted to have an in-memory cache (in Ruby) of a piece of data loaded on every request in order to prevent database access. It would have been a really hard problem for me to deal with at the time due to the lack of independence between the cache and the rest of the system. Sure, for some it might be easy to do, but I think that it’s significantly easier to reason about (and build) with an independent data lifecycle.

What we didn’t cover

There’s a lot of meaty and interesting topics that we looked at today. There’s even more stuff that we didn’t talk about. If you’re interested in learning more, here’s some topics you might want to look up. Maybe these will be covered in a part 2 of this post:

Thanks

Thanks to Mitch Hanberg, Chris Keathley, and Jess for reviewing this post and helping me clarify a few things.

The Book Plug

My book “From Ruby to Elixir” is available (beta) at The Pragmatic Bookshelf. This book teaches you Elixir and key libraries you need to write Elixir web apps.

From Ruby to Elixir by The Pragmatic Bookshelf
View other posts tagged: elixir genserver