Discussion about uses for Agent Processes


#1

I am collecting examples of uses for Agent. I would love to hear uses you can share. Thanks in advance!


To (Gen)Serve or not to (Gen)Serve
PragDave Fibonacci solution
How to store lots of data in memory?
Looking for clarity around using agent
#2

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.


#3

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… ^.^;


#4

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…


#5

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.


#6

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.


#7

I’ve been wondering the same thing myself. When do people actually use agent?


#8

I’d thought about using them as a low TTL query cache


#9

I don’t think I ever used Agent in one of my programs. That said, the use cases in the wild, I saw, are:


#10

I also don’t use Agents for the production runtime code. I did use them on few occasions in tests.


#11

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. :wink:

 @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

#12

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: []

#13
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.


#14

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.


#15

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.


#16

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.


#17

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.


#18

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.


#19

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.


#20

GenServers are already atomic though, their messages are fully serialized, so why have the extra Agent process?