Cuando trabajamos con Elixir y queremos construir aplicaciones más realistas, es fundamental entender cómo estructurar un árbol de supervisión que gestione múltiples procesos hijos. Para ello, podemos partir de un proyecto básico creado con la opción --sup, que nos genera un módulo Application con un árbol de supervisión vacío. Este árbol no hace nada por sí solo hasta que le añadimos hijos que supervisar.
Un primer paso sencillo es crear un GenServer que actúe como hijo de nuestra aplicación. Por ejemplo, podemos definir un módulo Prueba.Hola que use GenServer y tenga implementados los callbacks start_link e init. Al iniciar la aplicación, este GenServer se arrancará y ejecutará su función init, donde podemos incluir un simple mensaje para verificar que está funcionando.
Pero para acercarnos a un entorno más real, podemos añadir un segundo hijo que sea un servidor HTTP minimalista usando la librería Plug. Plug es una dependencia muy ligera que permite crear endpoints HTTP simples, similar a frameworks como Sinatra en Ruby o Flask en Python. Aunque Plug no es tan visible cuando trabajamos con Phoenix, está presente en su stack y es la base para manejar peticiones HTTP.
Para usar Plug, definimos un módulo que actúe como endpoint, por ejemplo Prueba.Endpoint. Este módulo debe implementar dos funciones: init/1, que puede dejarse vacía si no necesitamos inicialización especial, y call/2, que recibe una estructura conn con toda la información de la petición HTTP. En call/2 podemos modificar la respuesta, por ejemplo, estableciendo cabeceras y enviando un cuerpo con un mensaje simple como hola mundo.
defmodule Prueba.Endpoint do
import Plug.Conn
def init(options), do: options
def call(conn, _opts) do
conn
|> put_resp_content_type("text/html")
|> send_resp(200, "hola mundo")
end
end
Para que este endpoint funcione dentro de nuestra aplicación, debemos añadirlo al árbol de supervisión usando Plug.Cowboy, que es el adaptador HTTP que Plug utiliza para escuchar peticiones. En la lista de hijos de nuestro Application, incluimos algo así:
children = [
{Plug.Cowboy, scheme: :http, plug: Prueba.Endpoint, options: [port: 4001]}
]
Esto configura un servidor HTTP que escucha en el puerto 4001 y atiende las peticiones con nuestro módulo Prueba.Endpoint.
Un detalle crucial al ejecutar la aplicación es que, por defecto, cuando el proceso principal termina, toda la máquina virtual BEAM se detiene, lo que hace que el servidor HTTP deje de funcionar inmediatamente. Para evitar esto, debemos ejecutar la aplicación con la opción --no-halt, que mantiene la ejecución activa y permite que los procesos supervisados sigan corriendo.
Por ejemplo, en lugar de usar simplemente mix run, debemos usar:
mix run --no-halt
Esto es especialmente importante para aplicaciones que deben permanecer activas, como servidores web. De hecho, herramientas como Phoenix ya incorporan esta opción automáticamente cuando ejecutamos phx.server.
En desarrollo, sin embargo, no solemos usar mix run directamente, sino que trabajamos con iex -S mix, que lanza un intérprete interactivo con el entorno de la aplicación cargado y también mantiene el sistema activo. Esto nos permite modificar el código en caliente y recargar módulos sin reiniciar toda la aplicación, facilitando el desarrollo.
En resumen, al construir aplicaciones Elixir con supervisores y endpoints HTTP usando Plug, debemos tener en cuenta cómo estructurar el árbol de supervisión con múltiples hijos, cómo definir módulos Plug para manejar peticiones, y la importancia de ejecutar la aplicación con --no-halt para que el sistema no se detenga automáticamente. Así conseguimos un entorno robusto y persistente para nuestras aplicaciones.