Don’t put might-on-will.
Alan Cooper, About Face (1995), p.138
This design rule was specified in connection to user interfaces but over the years I’ve found it to be a good design rule in general. OO often pushed for more generic solutions - frequently in the name of improved reusability - without necessarily accounting for the increase in complexity or decrease in transparency that sometimes results from trying to account for another 5%(? or less) of use cases.
So while your alternate solution may seem more “generic” most use cases only ever need one or no message - not many.
-
Your concern can be easily addressed by composing the logic in
handle_call
with pure functions which can be tested separately instead of testinghandle_call
directly. -
If you are finding that you need to quite often dispatch multiple replies simply go with something like
defmodule PairUp do
use GenServer
defp multi_reply(msgs),
do:
Enum.each(msgs, fn({from, msg}) ->
GenServer.reply(from, msg)
end)
def handle_call({:pair, pid}, from, :none) do
{:noreply, {from, {:other, pid}}
end
def handle_call({:pair, pid}, from, msg) do
multi_reply([{from, {:other, pid}}, msg])
{:noreply, :none}
end
end
provide a pure interface.
Can you elaborate on what this means? In connection to functions “purity” is a well defined concept. Going from your post you seem to be largely concerned with message dispatch that isn’t handled via callback return values.
My typical solution is to implement the “business logic” in an entirely separate module and have the GenServer
callbacks simply invoke those module functions. So PairUp
would be simply GenServer
interaction logic (and GenServer
related helper functions) while some PairUpLogic
module would contain the actual “pure business functions”. So testing would focus on PairUpLogic
while PairUp
callbacks would mostly just call PairUpLogic
functions.