I believe there are two questions embedded here.
- Should a GenServer be registered under a local name?
- 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.