Cuando trabajamos con árboles de supervisión en Elixir, es fundamental entender cómo desacoplar la configuración del supervisor del acto de iniciarlo. Esto nos permite crear sistemas más flexibles y organizados, especialmente cuando tenemos supervisores anidados.
Al principio, solemos lanzar un supervisor con Supervisor.start_link/2, pasando una lista de hijos y opciones como la estrategia de reinicio. Por ejemplo, podemos tener dos procesos hijos, como una calculadora y una pila, cada uno definido con su ChildSpec. Sin embargo, esta forma de iniciar el supervisor mezcla la configuración con el inicio, lo que puede complicar la composición de supervisores más complejos.
Para solucionar esto, Elixir nos ofrece la función init/1 dentro del módulo supervisor. Esta función nos permite separar la declaración de la configuración del supervisor del momento en que se inicia. En concreto, init/1 recibe los argumentos necesarios y devuelve la configuración del supervisor, incluyendo la estrategia y la lista de hijos. Así, el supervisor se configura en un lugar y se inicia en otro, facilitando la reutilización y composición.
Por ejemplo, podemos definir la función init/1 en nuestro módulo supervisor para que reciba los argumentos y devuelva la configuración:
def init(args) do
children = [
supervisame.calculadora(),
supervisame.pila()
]
Supervisor.init(children, strategy: :one_for_one)
end
Luego, en lugar de llamar directamente a Supervisor.start_link/2 con toda la configuración, usamos Supervisor.start_link/3 (también conocido como start_link/3 o start_link/2 con módulo y argumentos), que recibe el módulo supervisor, los argumentos para init/1 y las opciones de inicio. Esto se conoce como start_link/3 o start_link/2 con módulo y argumentos, y permite que el supervisor se inicie llamando internamente a init/1 para obtener su configuración.
Así, el inicio del supervisor queda más limpio y desacoplado:
Supervisor.start_link(supervisame, :ok, name: :supervísame)
Aquí, :ok es el argumento que se pasa a init/1, y name: :supervísame es una opción para registrar el supervisor con un nombre.
Esta separación es especialmente útil cuando queremos anidar supervisores, es decir, tener un supervisor que supervise a otros supervisores. Si cada supervisor tiene su propia función init/1 que devuelve su configuración, podemos combinar fácilmente estas configuraciones en un supervisor superior sin mezclar la lógica de inicio.
Para ilustrar esto, podemos crear un segundo supervisor que lance el primero como hijo, usando la forma tradicional de start_link/2 con la lista de hijos:
defmodule MainSupervisor do
use Supervisor
def start_link(args) do
Supervisor.start_link(__MODULE__, args, name: :main_supervisor)
end
def init(_args) do
children = [
{supervisame, :ok}
]
Supervisor.init(children, strategy: :one_for_one)
end
end
En este ejemplo, MainSupervisor tiene como hijo al supervisor supervisame, que se inicia con el argumento :ok. Gracias a que supervisame implementa init/1, el supervisor principal puede iniciar a sus supervisores hijos sin preocuparse por su configuración interna.
Además, al registrar los supervisores con nombres, podemos referenciarlos fácilmente en otras partes de la aplicación, lo que facilita la gestión y el acceso a los procesos supervisados.
Es importante destacar que, aunque podemos omitir la implementación de init/1 y pasar toda la configuración directamente a start_link/2, seguir esta convención facilita la composición y el mantenimiento de sistemas con múltiples supervisores. Por eso, es recomendable implementar init/1 en supervisores descendientes y usar start_link/3 para iniciar supervisores con argumentos.
Finalmente, en la aplicación principal, que es el supervisor raíz, podemos usar la forma tradicional de iniciar el supervisor con start_link/2 y pasar la configuración directamente, ya que es el punto de entrada del sistema. Para los supervisores internos, en cambio, es mejor seguir la convención de init/1 y start_link/3 para mantener el desacoplamiento y la flexibilidad.
Con estas estrategias, podemos construir árboles de supervisión en Elixir que sean modulares, fáciles de mantener y que sigan las mejores prácticas recomendadas por la documentación oficial.