Hello there,
Been lurking for awhile, first time posting. I’ve checked for guidelines briefly, but please let me know if I should move this elsewhere, or format differently, etc.
Context
The last few days I’ve been toying around with Elixir and OTP. As a little project to learn with, I’ve been porting a turn based card game I wrote in Ruby to support multiplayer gameplay.
I have a bare bones version of it up and running but I’m looking for some feedback on the design.
Here’s a rough outline of the current iteration of my OTP application:
/application
/Registry
/GamesSupervisor (DynamicSupervisor)
/GameSupervisor (Supervisor)
/GameServer (GenServer)
/PlayersSupervisor (DynamicSupervisor
/PlayerServer (GenServer)
At a super high level, as you can probably guess, the application starts up the Registry
, GamesSupervisor
and PlayersSupervisor
.
The GamesSupervisor
is responsible for creating new games via GamesSupervisor.create_game(<game_id>)
. This is done by starting a GameSupervisor
, which starts a GameServer
that gets registered using the Registry
. The GameServer
holds the state of the game (turn, board state, current choice, etc).
The GameServer
exposes a join_game(<pid>, <player_name>)
“call” function that tells the PlayersSupervisor
to start a PlayerServer
. Many players can join a game, and the PlayerServer
is used for bidirectional communication between the game server and the individual players (e.g presenting choices and making decisions).
My high level goal is to isolate failure (in this case bugs in the game) to an individual game. For instance, if the game erroneously allows the player to choose an invalid choice (according to the game logic) it would not impact other games.
Question(s)
DynamicSupervisor Placement
The first thing I’ve gotten hung up on is where the PlayersSupervisor
should go in the hierarchy. My intuition tells me it should be a child of GameSupervisor
because the PlayerServer
processes that it ends up starting are conceptually part of the game?
However, it’s not clear to me if there is a benefit to nesting the dynamic PlayersSupervisor
under GameSupervisor
. I’ve not come across any examples of folks having multiple instances (processes) of a dynamic supervisor. Seems possible, just requiring a more dynamic process name to avoid the “already started” error.
Registry Usage
Is it common to use the registry to registry multiple types (for lack of better word) of processes or would you create multiple named registries? For example, registering processes that represent my GameServer
alongside PlayerServer
within the same registry.
External Interface
From the reading I’ve been doing, it seems common to expose a function on your supervisor as the entry point (e.g GamesSupervisor.create_game(<game_id>)
.
While this makes sense to me, I’m currently planning on keeping the GamesSupervisor
interface small. Instead, I’m planning to interact with the GameServer
and PlayerServer
processes directly, relying on their processes being locatable via the registry.
So I guess my question is around convention; do folks tend to use the supervisor as the interface to dispatching to the processes it supervises? Maybe this is just personal preference?