Design pattern for a genserver that needs to get state from the database

Say you go the path of using a GenServer to handle your commands and mutate state of your entity (e.g. %Todo{}). The reason for doing so is to decouple your business logic of managing Todo’s from the database implementation details. (There’s a good discussion here).

If you use Ecto.Changeset and/or Ecto.Schema in your GenServer, or the state uses the database schema / changesets you lose that decoupling.

Here’s a rule of thumb: if your entities are simple like a Todo or a Post where pretty much all the logic is captured by create, read, update, and delete, just use Ecto to represent your entity. If CRUD is fine, build your functions and changesets as you see in the Phoenix/Ecto docs. Think about your context boundaries and you’ll be served well.

But what if your entity is not simply represented by CRUD? Here are some things to consider using other patterns like you described:

  • Run-time characteristics: Maybe your entity encapsulates a business process. It has a Status field. You’ve got state-machine use cases to consider. You can still use CRUD here, but you’ll need more domain specific commands than CRUD.
  • You have a persistence layer that isn’t as nice as Ecto. Your persistence/query layers are nasty enough you plan to switch out the database later. You really don’t want to do anything else other than change a config to switch the persistence strategy you’re using.
  • Your business logic calls for a representation of state that is different from what your well-designed relational database looks like.

Like what @peerreynders said, it’s about trade-offs and knowing what they are and when to use them. Using a behavior to decouple your database in a config is extra development. Using a GenServer as a command handler and persisting when necessary also has overhead in development time and complexity. In many cases the trade-offs aren’t worth it.

1 Like