kyde
Which approach is better having multiple `handle_call` callbacks when using `GenServer` behavior or using helper functions?
I have a module where am using GenServer. In this module the state is a list of items. The module exposes functions to filter these items according to various criteria.
My question is what is the best approach to solving this task. Would it be better to:
- Add multiple
handle_callcallbacks for each filter criterion. - Use the
list_itemsfunctions and then filter its return value using multiple helper functions instead of the multiplehandle_callcallbacks.
Here is a simple implementation of the first approach:
defmodule Repositories do
use GenServer
## API
def start_link(_opts) do
.GenServer.start_link(__MODULE__, :ok)
end
def list_items(pid) do
GenServer.call(pid, :list_items)
end
def filter_by_name(pid, repo_name) do
GenServer.call(pid, {:filter_by_name, repo_name})
end
# Other APIs here for filtering by other criteria
....
## Server
def init(:ok) do
initial_repos = #Fetch initial repos.
{:ok, initial_repos}
end
def handle_call({:filter_by_name, repo_name}, _from, state) do
matching_repos = Enum.filter(state, fn repo -> String.equivalent?(repo.name, repo_name) end)
{:reply, matching_repos, state}
end
def handle_call(:list_items, _from, state) do
{:reply, state, state}
end
# Other callbacks here for handling calls to filter by the other various criteria
....
end
Here is a simple implementation of the second approach:
defmodule Repositories do
use GenServer
## API
def start_link(_opts) do
.GenServer.start_link(__MODULE__, :ok)
end
def list_items(pid) do
GenServer.call(pid, :list_items)
end
def filter_by_name(pid, repo_name) do
pid
|> list_items()
|> Enum.filter(fn repo -> has_name?(repo.name, repo_name) end)
end
# Other APIs here for filtering by other criteria
....
## Server
def init(:ok) do
initial_repos = #Fetch initial repos.
{:ok, initial_repos}
end
def handle_call(:list_items, _from, state) do
{:reply, state, state}
end
## Helper functions
def has_name?(actual_name, repo_name) do
String.equivalent?(actual_name, repo_name)
end
# Other helper functions to perform filtering by other criteria
....
end
Which approach is more favored in the elixir community?
Most Liked
al2o3cr
The data structure you return from handle_call is copied back to the calling process. If state is large, this will make the second approach significantly more expensive to execute.
A third approach worth considering: have one handle_call that accepts a filter function:
def filter_by_name(pid, repo_name) do
list_items(pid, fn repo -> has_name?(repo.name, repo_name) end)
end
def list_items(pid, filter \\ fn x -> x end) do
GenServer.call(pid, {:list_items, filter})
end
def handle_call({:list_items, filter}, from, state) do
{:reply, Enum.filter(state, filter), state}
end
This avoids copying state to the calling process by instead bringing filter to the data.








