Gerenciamento de Estado no Elixir: Processos, Agentes e GenServers em Ação
Rafael Andrade

Rafael Andrade @actor-dev

About: I'm Rafael the Actor Dev and I like to talk about Actor Models, Brighter, Elixir & Design Patterns Eu me chamo Rafael o Actor Dev e eu gosto de falar sobre Actor Models, Brighter, Elixir & Design Pat

Location:
London, UK
Joined:
Jan 24, 2025

Gerenciamento de Estado no Elixir: Processos, Agentes e GenServers em Ação

Publish Date: Jun 27
1 0

Introdução

Quando comecei a aprender Elixir, me perguntava como gerenciar estado. Diferentemente de linguagens imperativas com variáveis globais mutáveis, o modelo de dados imutáveis do Elixir e seu design orientado à concorrência (por meio da máquina virtual BEAM) exigem uma abordagem diferente. Neste artigo, explorarei como o estado é tratado em Elixir.

Contexto: BEAM VM e Concorrência

O Elixir roda na máquina virtual BEAM, projetada para alta concorrência e tolerância a falhas. Inspirada no Modelo de Ator, a BEAM trata processos como entidades leves que se comunicam por meio de passagem de mensagens. Como os dados são imutáveis, as alterações de estado são feitas criando novos valores em vez de modificar os existentes. Isso garante segurança em threads e simplifica a programação concorrente.

Loops Recursivos

A maneira mais simples de manter estado é implementando um loop recursivo. Aqui está um exemplo:

defmodule StatefulMap do
  def start do
    spawn(fn -> loop(%{}) end)
  end

  def loop(current) do
    new =
      receive do
        message -> process(current, message)
      end

    loop(new)
  end

  def put(pid, key, value) do
    send(pid, {:put, key, value})
  end

  def get(pid, key) do
    send(pid, {:get, key, self})

    receive do
      {:response, value} -> value
    end
  end

  defp process(current, {:put, key, value}) do
    Map.put(current, key, value)
  end

  defp process(current, {:get, key, caller}) do
    send(caller, {:response, Map.get(current, key)})
    current
  end
end
Enter fullscreen mode Exit fullscreen mode

Uso:

pid = StatefulMap.start() # PID<0.63.0>
StatefulMap.put(pid, :hello, :world)
StatefulMap.get(pid, :hello) # :world
Enter fullscreen mode Exit fullscreen mode

Agentes (Agents)

Outra opção é o módulo Agent; ele permite compartilhar estado entre diferentes processos ou no mesmo processo ao longo do tempo.

Implementação de exemplo:

defmodule Contador do
  use Agent

  def start_link(valor_inicial) do
    Agent.start_link(fn -> valor_inicial end, name: __MODULE__)
  end

  def valor do
    Agent.get(__MODULE__, & &1)
  end

  def incrementar do
    Agent.update(__MODULE__, &(&1 + 1))
  end
end
Enter fullscreen mode Exit fullscreen mode

Uso:

Contador.start_link(0)
#=> {:ok, #PID<0.123.0>}

Contador.valor()
#=> 0

Contador.incrementar()
#=> :ok

Contador.incrementar()
#=> :ok

Contador.valor()
#=> 2
Enter fullscreen mode Exit fullscreen mode

Para iniciar, recomenda-se usar um supervisor:

children = [
  {Contador, 0}
]

Supervisor.start_link(children, strategy: :one_for_all)
Enter fullscreen mode Exit fullscreen mode

GenServer

A opção mais clássica é o comportamento GenServer (similar a interfaces em .NET/Java), que permite gerenciar estado com requisições síncronas e assíncronas.

Callbacks principais:

  • init/1 -> quando o ator é iniciado.
  • handle_call/2 -> requisição síncrona (ex.: espera resposta).
  • handle_cast/3 -> requisição assíncrona (ex.: envio sem resposta).

Exemplo de GenServer:

defmodule Pilha do
  use GenServer

  # Callbacks

  @impl true
  def init(elementos) do
    estado_inicial = String.split(elementos, ",", trim: true)
    {:ok, estado_inicial}
  end

  @impl true
  def handle_call(:pop, _from, estado) do
    [para_cliente | novo_estado] = estado
    {:reply, para_cliente, novo_estado}
  end

  @impl true
  def handle_cast({:push, elemento}, estado) do
    novo_estado = [elemento | estado]
    {:noreply, novo_estado}
  end
end
Enter fullscreen mode Exit fullscreen mode

Uso:

# Iniciar o servidor
{:ok, pid} = GenServer.start_link(Pilha, "hello,world")

# Este é o cliente
GenServer.call(pid, :pop)
#=> "hello"

GenServer.cast(pid, {:push, "elixir"})
#=> :ok

GenServer.call(pid, :pop)
#=> "elixir"
Enter fullscreen mode Exit fullscreen mode

Conclusão

O gerenciamento de estado no Elixir depende de processos e imutabilidade. Loops recursivos oferecem controle fundamental, o Agent simplifica o estado compartilhado, e o GenServer fornece concorrência robusta com integração a supervisores. Cada ferramenta atende a casos de uso distintos, desde contadores simples até lógicas de estado complexas.

Referências

Trabalhando com Estado e Processos em Elixir
GenServer
Agent

Comments 0 total

    Add comment