Is giving a GenServer `__MODULE__` as name a good practice?

Hello, I was reading the Elixir School page on GenServer and I see that in the examples they use __MODULE__ as the name. For instance:

defmodule SimpleQueue do
  use GenServer
  ...
  def start_link(state \\ []) do
    GenServer.start_link(__MODULE__, state, name: __MODULE__)
  end

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

I think I understand what it does, it looks to me that it’s effectively creating a singleton GenServer, but coming from the object oriented world I’m a bit reluctant in adding singletons to my codebase.

I was wondering if this is considered a good practice in the Elxir world or if there is any other recommended pattern.

I’m aware that the answer to this question is probably “it depends on what you’re trying to do” but I’m trying to understand if, in general, having GenServers with __MODULE__ as their name is considered to be good or not.

4 Likes

This is totally fine, as long as you are sure there will be not more than one of these GenServer at a time. Often this holds and just using the modules name is one of the easiest things to do for the system and the developer. It’s easy recognizable and to remember.

Perhaps other people having actually a keyboard at their hand instead of a mobile phone might give more in depth explanations.

3 Likes

I believe there are two questions embedded here.

  1. Should a GenServer be registered under a local name?
  2. If yes, is __MODULE__ a good choice?

I’ll answer the 2nd one first, because it’s easier. You could, in theory pick any arbitrary atom for the name, but I believe that a module atom is a good choice which ensures that the registered name is unique, since you can’t have two different modules bearing the same name. Personally, I use __MODULE__ as the name option very frequently.

The first question is a bit more complex. I also came to Elixir (well, Erlang) with a lot of OO background, and I had the same doubts myself. In fact, in my early work, I avoided registered names as much as I could.

Similar to you, I also consider locally registered processes as (local) singletons. However, while singletons are mostly bad in OO, registered processes are more frequently used in Erlang/Elixir. The reason is that GenServers (sometimes called actors) are more similar to services, then they are to objects.

A GenServer (or any other kind of process) is more like a separate program, and therefore it can be regarded as a small service in the system. Just like with modern microservices, the communication between different processes is based on messaging. In order to send a message, a client service needs the address of the target (server) service. In the case of processes, that address is a pid.

However, keep in mind that processes might get restarted, and a restarted process gets a different pid. If the client is keeping a pid of the server process, it might end up with a stale pid, and it can’t talk to the server process anymore. A named registration helps you there, because clients don’t care about the exact address, they use the symbolic name to communicate with the server process. It’s kind of like a domain name, with pids being more like IP addresses.

Of course, this has its downsides too. In your example, you can have at most one queue per BEAM instance, which can seem quite restrictive. If you want to run more instances, then you can use Registry, which allows you to have arbitrary term as the name. Then, you can give names such as {__MODULE__, 1}, {__MODULE__, 2}, … to different queue instances.

Either way, the name corresponds to the role the process has in the system. The name SimpleQueue means that the process is the queue (the one and only in the BEAM instance). The name {SimpleQueue, 1} would represent a queue with the ID 1. With such naming, you can have more queues in the system, but there can be at most one per each ID.

Going beyond, you can also give so called global names, which have scope in the entire cluster.

Finally, we don’t always register our processes. For example, I sometimes rely on plain pids if the client and the server are tightly coupled to the point that one can’t exist without the other.

I’ve elaborated some more on the topic in this talk. It’s somewhat outdated, because since then we’ve got Registry in Elixir (so my examples with :gproc are not so relevant), but the ideas are still pretty much the same.

24 Likes

One thing to additionally consider is testing. We’re spoiled in Elixir by the concurrency, so whenever I’m writing systems, I’m thinking how can I test them concurrently. This usually also turns out to be helpful when later scaling the system, but that’s another story.

Even, if I ultimately will run the process registered under a local name, I will rarely register the name directly in start_link - instead I will accept an argument with a name. This can be later used to pass the name from the supervisor to create a named actor for the production system and integration testing. At the same time, this allows easily spawning additional processes in tests for unit testing the particular process. This is a bit more verbose, because it requires passing in the process name/pid as an additional argument to the functions, but this isn’t that big price to be paid and can be worked around as well (the simplest solution being a default argument).

10 Likes

Thank you all for your answers, they helped me a lot in understanding how I everything works in Elixir and how I should structure my applications.

I wasn’t thinking at GenServers (or processes in general) as services and the issues pids have when they get restarted. From that perspective everything makes sense.

2 Likes

I aim for testability in much the same way. Between a default options argument and Keword.put_new/3 with the module as the fallback name you get the best of both worlds.

4 Likes