Cuando queremos crear una pequeña aplicación web en Elixir, especialmente un microservicio, a veces Phoenix puede resultar demasiado pesado y complejo para lo que necesitamos. Por suerte, Plug nos ofrece una alternativa mucho más ligera y sencilla para construir aplicaciones web con menos código y sin tantas dependencias.
Plug es una biblioteca que nos proporciona una API cómoda y componible para manejar peticiones HTTP. Nos permite escribir el código que se ejecuta cuando alguien hace una petición a nuestra aplicación web, pero no incluye un servidor web por sí misma. Para eso, necesitamos un servidor externo, y en el ecosistema Elixir los más comunes son Cowboy y Bandit. Cowboy es el veterano y más extendido, aunque no está escrito en Elixir, mientras que Bandit es más reciente y está completamente desarrollado en Elixir, con la intención de ser una alternativa moderna. En este caso, optamos por Cowboy por su estabilidad y popularidad.
Para conectar Plug con Cowboy utilizamos la biblioteca plugcowboy, que actúa como una capa de integración entre ambos. Plug nos da las herramientas para escribir aplicaciones web, Cowboy es el servidor que las sirve, y plugcowboy es el pegamento que une estos dos mundos. Si en el futuro decidimos cambiar Cowboy por Bandit, solo tendríamos que modificar esta capa de integración sin tocar el resto de nuestro código basado en Plug. Esto es posible porque Phoenix, por ejemplo, está construido sobre Plug, lo que garantiza cierta estabilidad y compatibilidad a largo plazo.
Para empezar a construir nuestro microservicio, creamos un nuevo proyecto con mix new usando la opción --sup para que incluya un supervisor. Esto nos permite manejar el ciclo de vida de nuestra aplicación de forma robusta. Luego, en el archivo mix.exs, añadimos las dependencias plug y plug_cowboy. Aunque plug_cowboy ya incluye Cowboy, preferimos declarar explícitamente ambas para tener claridad sobre lo que usamos.
Después de descargar las dependencias con mix deps.get y compilar con mix run, podemos comenzar a escribir nuestro handler. Creamos un módulo nuevo, por ejemplo Microservicio.Ruta, donde definimos el comportamiento de nuestro endpoint. Importamos Plug.Conn para tener acceso a las funciones que nos facilitan manipular la conexión HTTP.
En este módulo definimos dos funciones esenciales: init/1 y call/2. La función init recibe opciones iniciales y nos permite preparar el estado de nuestro handler. En este caso, simplemente imprimimos un mensaje para confirmar que se ejecuta y devolvemos las opciones tal cual. La función call es la que se ejecuta con cada petición HTTP. Recibe la estructura conn, que representa toda la información de la conexión, incluyendo método, ruta, parámetros, cabeceras y más, y las opciones inicializadas.
Dentro de call, podemos hacer lo que necesitemos: consultar bases de datos, llamar a otros servicios, o simplemente responder. Para enviar una respuesta usamos send_resp/3 de Plug.Conn, pasando la conexión, el código HTTP (por ejemplo, 200) y el cuerpo de la respuesta. En nuestro ejemplo, devolvemos un simple hola mundo\n. También imprimimos la estructura conn para ver toda la información que contiene.
Para que nuestro handler funcione, lo añadimos al árbol de supervisión en application.ex. Usamos Plug.Cowboy.child_spec/1 para indicarle al supervisor que gestione nuestro plug en el puerto 4000 con el esquema HTTP. Así, cuando iniciemos la aplicación con mix run --no-halt, el servidor queda activo y listo para recibir peticiones.
Al visitar http://localhost:4000 en el navegador, recibimos la respuesta hola mundo y en la consola vemos impresa la estructura completa de la conexión, que incluye parámetros de consulta, cookies, cabeceras y más. Esto nos muestra cómo Plug nos da acceso total a la petición y nos permite manipular la respuesta de forma flexible.
Con esta base, podemos empezar a construir microservicios sencillos en Elixir sin la complejidad de Phoenix, aprovechando la ligereza y modularidad de Plug y Cowboy. En siguientes pasos podríamos, por ejemplo, gestionar parámetros de consulta para personalizar respuestas, manejar rutas diferentes o integrar bases de datos, todo con un código compacto y claro.