I am collecting examples of uses for Agent
. I would love to hear uses you can share. Thanks in advance!
Oh Iād love to hear some good use cases too. I sort of feel guilty not using Agent at all. I usually grab GenServer instead without much thinking.
Yeah, same here, I always have āsomeā functionality associated with shared state like that, so a GenServer has always seemed better, otherwise for shared state without functionality then I always use ETS/Mnesia insteadā¦ ^.^;
I have noticed one place that I tend to use it. Iāll share that later, so as not to bias the answers of others just yetā¦
I find it interesting that not many folks are chiming in with answers yet. I wonder if that signifies that they donāt get a ton of usage.
For myself, I have found them useful in testing other processes. Iāve rigged processes to pull pre-arranged values from them and stored values representing work happening inside of a process that I could later write assertions against.
The one time I tried to use them in an application, I grew to regret it and later replaced them with GenServer
processes, for the functionality reasons noted by @OvermindDL1.
Oops, I did forget another decent usage I found for them!
I recorded the events of a simulation as they happened, then fetched and cleared all changes in the drawing code (using get_and_update()
). That worked fine.
Iāve been wondering the same thing myself. When do people actually use agent?
Iād thought about using them as a low TTL query cache
I donāt think I ever used Agent in one of my programs. That said, the use cases in the wild, I saw, are:
- accumulation of the commands during a Migration, so they can be properly reversed on rollback: https://github.com/elixir-ecto/ecto/blob/master/lib/ecto/migration/runner.ex
- mix config: https://github.com/elixir-lang/elixir/blob/master/lib/mix/lib/mix/config.ex
I also donāt use Agents for the production runtime code. I did use them on few occasions in tests.
Iām using an agent like KV.bucket: http://elixir-lang.org/getting-started/mix-otp/agent.html. In the following function Iām using an agent as āaccumulatorā. If thereās a handier solution (Iām not as experienced as most posters) I would like to hear.
@doc """
Validate the input `body` in the taskform, task identified with `element_id', `socket` is third param
"""
@spec validate_task(map, String.t, Socket.t) :: :ok
def validate_task(body, element_id, socket) do
Agent.start_link(fn -> [] end, name: :error_accumulator)
traverse = fn(data, trav) ->
case data do
[_|_] ->
Enum.map(data, &(trav.(&1,trav)))
%{"key" => name, "validate" => validation, "label" => label} ->
result = validate_item(body |> Map.fetch!(name),
label,
validation)
if result != "", do: Agent.update(:error_accumulator, &List.insert_at(&1, 0, result))
%{"components" => components} ->
trav.(components,trav)
%{"columns" => columns} ->
trav.(columns,trav)
%{"rows" => rows} ->
trav.(rows, trav)
_ ->
data
end
end
WorkflowAgent.get_user_tasks(socket.assigns.agent_pid)
|> Stream.filter(fn task -> Map.fetch!(task, :id) === element_id end)
|> Enum.at(0)
|> Map.fetch!(:formfields)
|> Enum.at(0)
|> Map.fetch!("components")
|> traverse.(traverse)
errors = Agent.get(:error_accumulator, &(&1))
Agent.stop(:error_accumulator, :normal)
case errors do
[] -> push socket, "cleanupFormIO", %{}
:ok
_ -> push socket, "alert", %{message: "<br>" <> (errors |> List.foldl(" ", fn(x, acc) -> x <> acc end)), type: "error"}
:errors_found
end
end
You seem to be using agent here to simulate a mutable object. I donāt think thatās a good/idiomatic example for using agents (or processes for that matter).
It appears that traverse
function here is used only to gather errors. You could write that in a pure functional way, by extracting a separate function which returns a list of errors. Maybe a function should be renamed to validate
or something along those lines to express its main purpose.
Hereās a non-tested attempt:
defp validate([_|_] = data) do
data
|> Stream.map(&validate/1)
|> Enum.concat()
end
defp validate(%{"key" => name, "validate" => validation, "label" => label}) do
case validate_item(body |> Map.fetch!(name), label, validation) do
"" -> []
error -> [error]
end
end
defp validate(%{"components" => components}), do: validate(components)
defp validate(%{"columns" => columns}), do: validate(columns)
defp validate(%{"rows" => rows}), do: validate(rows)
defp validate(_other_kind_of_data), do: []
You seem to be using agent here to simulate a mutable object. I don't think that's a good/idiomatic
example for using agents (or processes for that matter).
Thatās wat I was troubled about. Your solution is perfect, thanks. I only had to add input param body.
I actually think this is what Agent is, and it is a reason why we seem to avoid it in our toolbox. Quite rightfully so as well.
It is basically a shared mutable state, and a such itās usage should be controlled and limited.
I somewhere read the smart explanation: If you have only state, use agent, if you have only processing, use as task, if you have a mixture of both, use Genserver.
I am not using Agent since I prefer to have state in the db. We have the app in docker containers, so no erlang cluster. I think if I would use an erlang cluster then it would make more sense to use Agents to store state.
Iāve been reading āFunctional Web Development with Elixir and Phoenixā and I actually find the use cases over there quite appealing. You should check it out too.
EDIT: Ok, no Iām reading further and it does look awfully like I would defo go with GenServer.
I understood that in reality itās not shared mutable state, but I agree itās a simulation. We need to persist things in memory sometimes (be it in ets, an agent, genserver). In my āaccumulatorā case the use of an agent was not needed.
I also find difficult to argue for the use of Agents. They were introduced as a solution to store data that is simpler than a GenServer. Often though, there is logic related to that data which makes a compelling argument to use a GenServer instead.
The only reason I would be tempted to use an agent is when I feel the need for an ETS table. This is where an agent should be the first solution before being labelled as a bottleneck.
Iām using GenServers and Agents to effectively wrap structs. The Agent handles the state transformation atomically via get_and_update/3
whilst the GenServer handles the logic around those transformations.
GenServers are already atomic though, their messages are fully serialized, so why have the extra Agent process?