Agents are simple wrappers around state. They provide a basic mechanism to store and retrieve values through a process, making them perfect for managing shared state across your application.
Here’s a taste of Agents in action:
# Start an agent with initial state
{:ok, counter} = Agent.start_link(fn -> 0 end)
# Update state
Agent.update(counter, fn count -> count + 1 end)
# Get current value
current = Agent.get(counter, fn count -> count end) # Returns 1
While GenServer offers more features, Agents are perfect for simpler state management:
✅ Benefits of Agents:
# GenServer approach
defmodule Counter do
use GenServer
def init(initial), do: {:ok, initial}
def handle_call(:get, _from, state), do: {:reply, state, state}
def handle_cast({:increment}, state), do: {:noreply, state + 1}
end
# Agent approach (simpler!)
{:ok, counter} = Agent.start_link(fn -> 0 end)
Agent.update(counter, fn state -> state + 1 end)
Agent.get(counter, fn state -> state end)
defmodule UserPreferences do
def start_link do
Agent.start_link(fn -> %{} end, name: __MODULE__)
end
def get_preference(key) do
Agent.get(__MODULE__, fn prefs -> Map.get(prefs, key) end)
end
def set_preference(key, value) do
Agent.update(__MODULE__, fn prefs ->
Map.put(prefs, key, value)
end)
end
end
defmodule AtomicCounter do
def start_link do
Agent.start_link(fn -> 0 end, name: __MODULE__)
end
def increment do
Agent.get_and_update(__MODULE__, fn count ->
{count + 1, count + 1}
end)
end
def current do
Agent.get(__MODULE__, & &1)
end
end
defmodule Cache do
def start_link do
Agent.start_link(fn -> %{} end, name: __MODULE__)
end
def get(key) do
Agent.get(__MODULE__, fn state ->
case Map.get(state, key) do
nil -> {:miss, nil}
value -> {:hit, value}
end
end)
end
def put(key, value) do
Agent.update(__MODULE__, &Map.put(&1, key, value))
end
def clear do
Agent.update(__MODULE__, fn _state -> %{} end)
end
end
✅ Do:
❌ Don’t:
defmodule MyApp.Application do
use Application
def start(_type, _args) do
children = [
{Agent, fn -> initial_state() end, name: MyApp.StateAgent}
]
opts = [strategy: :one_for_one]
Supervisor.start_link(children, opts)
end
defp initial_state, do: %{}
end
defmodule SafeAgent do
def get_value(agent, key) do
try do
Agent.get(agent, fn state ->
Map.get(state, key)
end)
catch
:exit, _ -> {:error, :agent_down}
end
end
def update_safely(agent, key, value) do
try do
Agent.update(agent, fn state ->
Map.put(state, key, value)
end)
catch
:exit, _ -> {:error, :agent_down}
end
end
end
✅ Great for:
❌ Consider alternatives for:
# Bad: Complex computation inside Agent
Agent.get(agent, fn state ->
Enum.reduce(state, 0, fn {_k, v}, acc ->
acc + complex_calculation(v)
end)
end)
# Good: Get data and process outside
data = Agent.get(agent, fn state -> state end)
Enum.reduce(data, 0, fn {_k, v}, acc ->
acc + complex_calculation(v)
end)
Try this simple example in iex
:
# Start an Agent to store a shopping cart
iex> {:ok, cart} = Agent.start_link(fn -> [] end)
# Add items
iex> Agent.update(cart, fn items -> items ++ ["milk"] end)
:ok
iex> Agent.update(cart, fn items -> items ++ ["bread"] end)
:ok
# Check contents
iex> Agent.get(cart, fn items -> items end)
["milk", "bread"]