¿Qué es un GenServer?

El GenServer es una estructura de alto nivel construida por encima de la API de Procesos de Elixir para facilitar el uso de procesos en los cuales se envían mensajes y se gestionan estados. En este vídeo empezamos viendo init y handle_info, funciones útiles para empezar a trabajar con procesos. Además, de refilón: ¿os habéis fijado que con pid/1 puedo obtener un PID dado un string con sus tres números? ¿y que con r() puedo recompilar sobre la marcha mediante hot swap un módulo en IEx?

Cuando trabajamos con procesos en Elixir, pronto nos damos cuenta de que manejar todo a mano con receive, send, spawn y demás puede ser tedioso y propenso a errores. Por ejemplo, si tenemos una calculadora distribuida que lleva un contador de operaciones para facturar, debemos encargarnos manualmente de incrementar ese contador, gestionar las llamadas recursivas para seguir recibiendo mensajes y evitar que el proceso se quede bloqueado esperando mensajes que nunca llegan. Todo esto hace que el código sea más complejo y menos fiable.

Para facilitarnos la vida, Elixir nos ofrece módulos de alto nivel que envuelven la gestión de procesos y nos permiten trabajar con ellos de forma más segura y sencilla. Entre estos, los GenServers son una pieza fundamental. Un GenServer, o servidor genérico, es un proceso que actúa como un servidor al que podemos enviarle peticiones y recibir respuestas, muy parecido a cómo funcionan los servidores web. Esto nos permite delegar operaciones, como cálculos, a un proceso especializado que mantiene su propio estado interno.

Para crear un GenServer, definimos un módulo que use GenServer. Esto importa automáticamente un comportamiento que nos obliga a implementar ciertas funciones con nombres específicos para que Elixir sepa cómo interactuar con nuestro proceso. La función más importante que debemos definir es init, que se ejecuta cuando arrancamos el GenServer. Esta función recibe un parámetro inicial y debe devolver una tupla con :ok y el estado inicial del servidor. Este estado es persistente y se pasa a las funciones que gestionan los mensajes, permitiéndonos mantener y modificar datos internos como el contador de operaciones.

Por ejemplo, podemos iniciar un GenServer con un estado inicial que sea un mapa con un contador a cero:

defmodule GColk do
  use GenServer

  def init(param) do
    IO.puts("Inicio GenServer GColk")
    IO.inspect(param)
    {:ok, %{contador: 0}}
  end
end

Para arrancar este GenServer, usamos GenServer.start_link/2, pasando el módulo y el parámetro inicial:

{:ok, pid} = GenServer.start_link(GColk, {:hola, :buenas})

Una vez que el GenServer está en marcha, podemos enviarle mensajes para que los gestione. La función handle_info/2 es la encargada de recibir mensajes enviados con send/2. Esta función recibe el mensaje y el estado actual, y debe devolver una tupla con :noreply y el nuevo estado. Si no queremos modificar el estado, simplemente devolvemos el mismo.

def handle_info(msg, state) do
  IO.puts("Me mandan mensaje #{inspect(msg)}")
  {:noreply, state}
end

Cada vez que llega un mensaje, handle_info se ejecuta con el estado actualizado, lo que nos permite mantener un estado mutable de forma segura y controlada. Además, Elixir nos permite recargar el código de un módulo en caliente con la función r, lo que actualiza todos los GenServers que usan ese módulo sin necesidad de reiniciar el sistema.

Aunque handle_info es útil para mensajes enviados con send, no es la forma más habitual de interactuar con un GenServer. Normalmente, usaremos las funciones call y cast que proporcionan una interfaz más estructurada para enviar mensajes sincrónicos y asincrónicos respectivamente, y que nos permiten manejar respuestas de forma más clara. Pero eso lo veremos en detalle más adelante.

Con GenServers, podemos construir procesos que gestionan su propio estado y responden a mensajes de forma ordenada y segura, evitando tener que lidiar con los detalles bajos de receive y send. Esto hace que trabajar con concurrencia en Elixir sea mucho más accesible y robusto.

Lista de reproducción
  1. 1
    Cómo crear procesos
    11 minutos
  2. 2
    Cómo pasar mensajes entre procesos
    13 minutos
  3. 3
    Diccionario de un proceso y mantener un estado
    15 minutos
  4. 4
    Cómo enlazar procesos para detectar fallos
    13 minutos
  5. 5
    Cómo monitorizar procesos
    10 minutos
  6. 6
    ¿Qué es un GenServer?
    15 minutos
  7. 7
    Cómo enviar mensajes con un GenServer
    19 minutos
  8. 8
    Control de errores y gestión de un GenServer
    19 minutos
  9. 9
    Cómo renombrar procesos
    11 minutos
  10. 10
    Cómo crear un Supervisor Tree
    17 minutos
  11. 11
    Estrategias para trabajar con Supervisor
    18 minutos
  12. 12
    Estrategias para crear un Supervisor
    11 minutos
  13. 13
    Resumen sobre procesos OTP
    12 minutos
  14. 14
    Cómo usar Application
    12 minutos
  15. 15
    Ejemplo de Application con hijos
    14 minutos