I'm not sure that I've found a good reference for this, but its been 4 years since I went through that learning process. There are two basic approaches for starting GenServers. First, you can start them from a supervisor that runs at startup. In this care they will be named (which is not your question).
The second approach is to start them from another stateful process (Another GenServer, GenFSM, or GenStatem. In this case you will need to store the pid as part of that server's state. When I need to have multiple processes access a common GenServer, I will create a "Manager" that uses a
Map to map an id to the PID.
The other I have done is to propagate the pid to all processes that need the pid. But then you need to handle updating that pid if the server restarts.
This isn't too difficult, you just need to trap exits on the process so you will receive a :DOWN message.
Also, when starting dynamic servers, I don't usually run
start_link directly, but create an API on a supervisor to start the process.
One thing that you have to watch out for is providing a pid in the start up arguments of a supervised GenServer. When a supervised process restarts, it receives the original arguments provide on its initial startup. So, if you provide the pid of some process and that process has restarted, then the pid will be stale. I remember struggling with this for a while when I was starting out with elixir.