Elixir Agents

On OTP (Erlang included) we have the concept of a GenServer, that mostly stands for generic server. On Elixir we also have two kind of servers that are not that generic: Agent and Task. Both are implemented using GenServer, but more specific. Agents were created to be a server focused on store data and Task to compute data.

Recently, I've decided to take a better look on Agents and I must admit that I started to like it. I'm use then to play around with some small projects, instead of store data in a full featured DB, it's been a good way to play around.

Agent 101

Agent's a simple process that stores and retrieves data given function. For example.

# Starts an agent proccess with a new MapSet
Agent.start_link(fn -> MapSet.new end, name: :people)  
# {:ok, #PID<0.59.0>}

# Put some data on the Mapset
Agent.update(:people, fn(people_set) ->  
  MapSet.put people_set, %{name: "John Doe"}
end)  
# :ok
Agent.update(:people, fn(people_set) ->  
  MapSet.put people_set, %{name: "Charlie Brown"}
end)  
# :ok
Agent.update(:people, fn(people_set) ->  
  MapSet.put people_set, %{name: "Pedro Medeiros"}
end)  
# :ok

# Recover the data once inserted
Agent.get(:people, fn(people_set) -> people_set end)  
# #MapSet<[%{name: "Charlie Brown"}, %{name: "John Doe"}, %{name: "Pedro Medeiros"}]>

As we can see we start a process giving it a name (people) and when we need to do any operation on our agent named :people, all we need to do is pass the atom as the first argument, which can also be a PID of the process.

Now that we have the basics of Agents, let's move on creating a new Agent which is unique for our application. let's call this module PeopleStore. Turns out that Module names are atoms on elixir, so we can easily use then as the name of our Agent.

# people_store.ex
defmodule PeopleStore do  
  def start_link do
    Agent.start_link(fn -> MapSet.new end, name: __MODULE__)
  end
end  

Now that we have the agent up and running when we run start_link, let's do some operational over it. Notice that we used __MODULE__ syntax, which is the equivalent to the model name PeopleStore

# people_store.ex
# ...
  def add_person person do
    __MODULE__
      |> Agent.update(fn people -> 
           MapSet.put people, person
         end)
  end

Let's say that our users has names that is unique, and we want to retrieve a person based on a name field. We can achieve this by implement our Agent.get/2 with Enum.find/2

# people_store.ex
# ...

  def get_person name do
    __MODULE__
      |> Agent.get(fn people ->
           Enum.find people, &(&1.name == name)
         end)
  end

On Enum.find function we used a shortcut function operator, which is just a syntax sugar for fn x -> x.name == name end. It's visible how it can be simple for small things, it uses &N to determine the argument position of the callback function.

Now with our PeopleStore we can store people and search for people based on name. I'm using it sometimes to fake some test data, It's really useful. And being a GenServer we known It won't have concurrency issues, once an Agent will only do one thing at the time.

Let's See our final Example on using our brand new PeopleStore

AgentStore.start_link #important to instanciate it in memory

AgentStore.add_person(%{name: "john doe"})  
AgentStore.add_person(%{name: "Foo bar"})  
AgentStore.add_person(%{name: "Dave"})

person = Agent.get_person("john doe")

IO.puts("just found #{person.name}")  
# Just found john doe