Genserver: should we prefer thin callbacks or thin interface functions?

Stylistic question:

When writing a genserver module, we often write a set of interface functions and (of course) the callbacks. Assume that there is some business logic (data manipulation of the received message, spawning multiple processes, etc).

Should that business logic be placed in the interface function, leaving a “thin” callback, or the other way around?

Especially if some of the business logic involves calling other interface functions, spawning tasks, etc.

This question to me goes far beyond style, because the performance characteristics and failure modes are super different.

Let me first make sure I am capturing your question properly. You’re comparing these situations

# logic in the interface function
def some_action(server, arg) do
  computed_value = # fun business logic
  GenServer.call(server, {:some_action, arg, computed_value})
end

vs

# logic in the callback function
def handle_call({:some_action, arg}, state) do
  computed_value = # fun business logic
  {:reply, blah}
end

Style here is the absolute last consideration. Code executed in the interface function is executed in the caller process whereas code executed in the callback is executed in the genserver. The primary question at hand is whether or not it needs to access or have atomic control over the genserver state. If so it has to go in the callback. If it doesn’t, then you’d likely favor the client pid to avoid bottlenecking the genserver, since the genserver can only handle 1 message at a time.

Each case has very different failure modes too. If an exception is raised in def some_action then this just crashes the caller pid, but the genserver remains. If you raise an exception in handle_call then this crashes both the genserver, and any currently linked caller pids.

11 Likes

It is not a style question but an architecture question. The point of a GenServer is to serialize and enforce state transition. So anything that need to be serialized should be in the callbacks. Anything that don’t should be in the interface function.

4 Likes