Modelling data in Elixir

How far would one go about breaking down ones domain model into separate processes? For example if I have something like a game, that has many different stateful entities and my application can contain many games. Which of the following ways would one model this in Elixir (or is there another option)?

  1. Each game is a GenServer and the state contains a deeply nested map containing the state of all other entities.
  2. Each game is a GenServer but also every individual entity is its own GenServer. (If this is the approach how does one tie all the entities to the correct game etc?)

From the Elixir example I have seen there is a lack of information on hos to handle more complex state like the one above. Most examples are just on stateful object. Like for example a simple game state modelled and a GenServer and maybe a DynamicSupervisor and Registry is used. But how do you actually break apart a more complex domain model into many?

process-per-entity might not be the best breakdown. To spawn or not to spawn has some advice.

3 Likes

Think of processes as a unit of fault tolerance.

It’s not generally useful to break down your game objects into separate processes, because if a game object crashes, the whole game state becomes inconsistent. Other game objects can’t ignore it and keep running.

However it is useful to have one process per game, because games are independent. A crash in one game should not affect the other games.

Sometimes the easiest solution may be to avoid having a long-lived process in the first place. For a simple turn-based game for instance, it might be totally OK to persist the game state in DB, and just fetch + write back whenever a player takes a turn.

2 Likes

My own .02$ (or perhaps a bit more)
This is completely dependent on the nature of the entities you’re trying to model and the game (in this case) itself. For instance I have only modelled a complex type of game state, and that was for a sequential, turn-based, limited “entities” type of game, a TCG.

In that case I believe modelling each game as a single genserver is what you want, because the actions are sequential and non-concurrent. The scope being limited, e.g. the resources (translated as keys and sections in a map) can’t grow indefinitely, also makes it a good fit for this type of structuring. Only one player at any given time is the “active” player, so holding everything in the state of one game genserver plays well with this.

I still want to be able to have the sequential message processing offered by process mailboxes, in this case genservers, so I use a that to hold state. This lets me have an entry point where to retrieve the current state, and then check for legality, and then proceed to transform the existing state into a new state reflecting the player action, bypassing more expensive calls to the DB. I end up dumping the state after each valid action into a db record, but usually the game is played out only through the genserver.

If on the other hand I was trying to model a complex world, where many different entities (regions, creatures, planets, wtv) can be interacted with by more than a player at the same time (say you have a planet that can be harvested for something by multiple players at the same time), then I would probably work on structuring those entities as independent servers. I would probably use named genservers (e.g. {:global, {:planet, “Some Cool Name”, game_id}}, {:global, {:player, player_id, game_id}}), that I could reconstruct whenever I needed to either retrieve state from the entity, mutate its state, or tell it to do something.

Then have these processes message each other accordingly. For instance a player does an action, “harvest”, in “Some Cool Name Planet”, by player_id, in game_id. I would pick up the genserver for the player, and send it a message for the corresponding action, in the corresponding game. It could check if it was valid, and if so, send then a message to the “planet entity” to receive the corresponding result of the action, then the “planet entity” could emit a message to the caller with “here you have 100 of iron”, adjust its state and stay there, or “there’s no more iron” (you would need to weight using calls, casting or messaging, although probably you would want messaging and build the remaining structure to be totally asynchronous).

Then the player genserver would take care of emitting this back to whatever needs (say the browser client). But not all entities should (as in they don’t benefit directly and will increase complexity without any added benefit) be modelled as processes. For instance if it’s a static inventory item you don’t want to model that as a process (unless its properties resemble that of an independent process). The reasons that should tip you over to structuring entities as genservers are relative to the type of interactions they should support and not any hard defined rule.

For instance if you have a single genserver that holds state of a world, and this world has many entities, let’s say planets to keep in line with the previous example, and there’s like 500 planets, and 500 players in this single game, this means that if you model it as a single genserver, any and each single interaction of any player on any planet would require the interaction to go through this single genserver, mutate it, etc, the same for data retrieval., even if they’re asking/messaging about totally different things, that in this case are only related because of they way you packed all the data in a single point and not because they’re naturally interdependent. This could become a bottleneck easily and as such probably a different structure would benefit it. But then you need to be more careful in structuring the game and entities, since they will need to work in tandem and does require a decent grasp on the semantics of message passing.

5 Likes

^^ great write-up. It most definitely depends on the particular game.

I don’t have much to add in terms of the question as I’m still new to Elixir, but perhaps this talk about simulating ant foraging using processes might inspire:

3 Likes