Concorrência & OTP - Parte 1

Começando com GenServer

Como o próprio nome sugere, GenServer é um servidor genérico, que nos permite criar processos e interagir com os mesmos através de uma série de callbacks.

Eis uma excelente definição encontrada na versão em português do elixirschool.

...GenServer é um único processo que roda um loop que processa uma mensagem por interação passando para frente um estado atualizado.

Sim, os processos GenServer também possuem seus próprios estados, estes são mantidos em memória até que o processo seja encerrado.

Como um GenServer funciona

Como o GenServer faz parte do OTP, ele já vem "embutido" por padrão quando você instala o Elixir(graças ao Erlang), ou seja, não há necessidade de instalar nenhuma dependência externa.

O Básico

Para exemplificar, vou criar um projeto com o comando:

$ mix new payment --sup

Agora a primeira coisa que irei fazer é criar um arquivo chamado de payment_server que será um módulo com comportamento de GenServer dentro de lib/payment/ com o seguinte:

# lib/payment/payment_server.ex

defmodule PaymentServer do
  use GenServer
end

Certo, a opção --sup serve para criar um supervisor, este se encontra em lib/payment/application e irá se parecer mais ou menos com isto:

# lib/payment/application.ex

defmodule Payment.Application do
  @moduledoc false

  use Application

  @impl true
  def start(_type, _args) do
    children = []
    opts = [strategy: :one_for_one, name: Payment.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

Task.Supervisor

Ok, por hora não vamos entrar em detalhes sobre o assunto dos supervisores. Apenas pense em um supervisor como um "cuidador/monitor" dos processos que são atribuídos a ele.

Então, por hora irei adicionar o módulo GenServer que acabei de criar na árvore de supervisão.

defmodule Payment.Application do
  @moduledoc false

  use Application

  @impl true
  def start(_type, _args) do
+    children = [
+      %{
+        id: Payment.ProcessTaskSupervisor,
+        start: {
+          Task.Supervisor,
+          :start_link,
+          [[name: Payment.ProcessTaskSupervisor]]
+        }
+      }
+    ]
+    opts = [strategy: :one_for_one, name: Payment.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

De volta ao módulo PaymentServer, ao adicionar a macro use GenServer no módulo PaymentServer é injetado automaticamente um @behaviour de GenServer em nosso módulo.

@behaviour, em uma tradução literal significa "comportamento", dentro do Elixir, são semelhantes à "interfaces ou annotations" comum em outras linguagens de programação, ou seja, é fornecido ao módulo todas as funções exigidas para se comportar como um GenServer.

Irei adicionar uma função chamada init/1 que é requerida para um processo GenServer, como o nome é bem sugestivo você já deve ter ideia do que se trata.

  # lib/payment/payment_server.ex

  defmodule PaymentServer do
    use GenServer

    def init(args) do
      IO.puts("INPUT => #{inspect(args)} ")

      {:ok, args}
    end
  end

Com o IEx, já consigo executar meu processo.

$ iex -S mix

>> GenServer.start(PaymentServer, "#myElixirStatus")

Received arguments: "#myElixirStatus"
{:ok, #PID<0.163.0>}

Aqui nós temos um retorno após a execução, que mostra na prática o que expliquei no início do post.

É válido observar que logo após iniciar o processo é retornado uma tuple com um atom:ok e um PID que serve como identificador único para cada processo.

{:ok, #PID<0.163.0>}

Você pode verificar usando a função self() no IEx, que número do PID retornado é diferente do PID do processo.

>> self()
#PID<0.141.0>

Claro, na sua máquina o PID pode ser outro.

Também é possível parar um processo usando o PID como referência. Com auxílio do pattern match irei armazenar o PID em uma variável para facilitar a reutilização.

>> {:ok, pid} = GenServer.start(PaymentServer, "#myElixirStatus")

Received arguments: "#myElixirStatus"
{:ok, #PID<0.143.0>}

>> GenServer.stop(pid)
:ok

Pronto! O GenServer está funcionando como deveria, porém, ele ainda não faz nada! 😅

Irei implementar mais funcionalidades à medida que for escrevendo os outros posts.

Link para o repositório.

Referências:

Comentários