Inyección de dependencias en JavaScript
Jerry Ejonavi
17 de noviembre de 2022
0 minutos de lecturaLas técnicas de inversión de control (IoC) permiten a los desarrolladores salir del flujo de programación tradicional y ofrecen más flexibilidad y control sobre el código. La inyección de dependencias, una forma de IoC, es un patrón que busca separar la creación de objetos de su uso.
En este artículo, aprenderás qué es la inyección de dependencias, cuándo usarla y en qué frameworks populares de JavaScript está implementada.
¿Qué es la inyección de dependencias?
La inyección de dependencias es un patrón de diseño de software en el que un objeto o una función usa otros objetos o funciones (dependencias) sin preocuparse por los detalles de su implementación interna. La tarea de proporcionar dependencias a los objetos que las necesitan se delega en un inyector, a veces llamado ensamblador, proveedor o contenedor.
Por ejemplo, piensa en cómo las consolas de videojuegos solo necesitan un disco o cartucho compatible para funcionar. Los distintos discos o cartuchos suelen contener información de diferentes juegos. El jugador no necesita saber nada sobre el funcionamiento interno de la consola y, por lo general, solo inserta o cambia los discos para jugar.
Diseñar software o escribir código de esta manera facilita quitar o reemplazar componentes (como en el ejemplo anterior), escribir pruebas unitarias y reducir el código repetitivo en una base de código.
Aunque la inyección de dependencias es un patrón que se implementa y usa comúnmente en otros lenguajes, sobre todo Java, C# y Python, es menos frecuente en JavaScript. En las siguientes secciones, conocerás las ventajas y desventajas de este patrón, su implementación en frameworks populares de JavaScript y cómo configurar un proyecto de JavaScript teniendo en cuenta la inyección de dependencias.
¿Por qué necesitas la inyección de dependencias?
Para comprender por completo por qué resulta útil la inyección de dependencias, es importante entender los problemas que resuelven las técnicas de IoC. Por ejemplo, retomemos la analogía de la consola de videojuegos; intenta representar con código la funcionalidad de una consola. En este ejemplo, la llamarás NoSleepStation y trabajarás con los siguientes supuestos:
Una consola NoSleepStation solo puede reproducir juegos diseñados para NoSleepStation.
La única fuente de entrada válida para la consola es un disco compacto.
Con esta información, se podría implementar la consola NoSleepStation de la siguiente manera:
Aquí, la lógica principal de la consola está en la clase GameReader, y tiene una dependencia: NSSConsole. El método play de la consola inicia un juego mediante una instancia de GameReader . Sin embargo, aquí se observan algunos problemas, entre ellos, la flexibilidad y las pruebas.
Flexibilidad
El código mencionado anteriormente es inflexible. Si un usuario quisiera jugar otro juego, tendría que modificar la clase NSSConsole, como si desarmara la consola en la vida real. Esto se debe a que una dependencia principal, la clase GameReader, está codificada directamente en la implementación de NSSConsole.
La inyección de dependencias resuelve este problema al desacoplar las clases de sus dependencias y proporcionar estas últimas solo cuando se necesitan. En el ejemplo de código anterior, la clase NSSConsole solo necesita el método readDisc() de una instancia de GameReader.
Con la inyección de dependencias, se puede reescribir el código anterior de esta manera:
El cambio más importante en este código es que las clases NSSConsole y GameReader están desacopladas. Aunque NSSConsole sigue necesitando una instancia de GameReader para funcionar, no tiene que crearla explícitamente. La tarea de crear la instancia de GameReader y pasarla a NSSConsole se delega en el proveedor de inyección de dependencias.
En una base de código grande, esto puede ayudar considerablemente a reducir el código repetitivo, ya que el proveedor de inyección de dependencias se encarga de crear y conectar las dependencias. Así, no tienes que preocuparte por crear instancias de las clases que otra clase necesita, sobre todo si esas dependencias tienen sus propias dependencias.
Pruebas
Una de las ventajas más importantes de la inyección de dependencias se observa en las pruebas unitarias. Al delegar en un proveedor externo la tarea de proporcionar las dependencias de una clase, se pueden usar mocks o stubs en lugar de objetos que no se están probando:
Siempre que la interfaz de la dependencia (los métodos y las propiedades expuestos) no cambie, se puede usar un objeto sencillo con la misma interfaz en lugar de una instancia real para realizar pruebas unitarias.
Limitaciones de la inyección de dependencias
Es importante tener en cuenta que la inyección de dependencias también tiene desventajas, que suelen estar relacionadas con la forma en que intenta resolver el problema de reducir el código repetitivo.
Para aprovechar al máximo la inyección de dependencias, sobre todo en una base de código grande, es necesario configurar un proveedor de inyección de dependencias. Configurarlo puede requerir mucho trabajo y, en ocasiones, es una carga innecesaria en un proyecto sencillo.
Este problema se puede resolver con una biblioteca o un framework de inyección de dependencias. Sin embargo, en la mayoría de los casos, usar un framework de este tipo requiere adoptarlo por completo, ya que los distintos frameworks de inyección de dependencias tienen pocas similitudes entre sus API de configuración.
Inyección de dependencias en frameworks populares de JavaScript
La inyección de dependencias es una función clave de muchos frameworks populares de JavaScript, en particular Angular(), NestJS y Vue.js.
Angular
Angular es un framework de desarrollo web del lado del cliente que incluye las herramientas necesarias para crear, probar y mantener aplicaciones web. La inyección de dependencias está integrada en el framework, por lo que no hace falta configurarla y es la forma recomendada de desarrollar con Angular.
A continuación, se muestra un ejemplo de código sobre cómo funciona la inyección de dependencias en Angular (tomado de la documentación de Angular):
En Angular, el uso del decorador @Injectable en una clase indica que esta se puede inyectar. Una clase inyectable puede ponerse a disposición de las dependencias de tres maneras:
A nivel de componente, mediante el campo
providersdel decorador@ComponentA nivel de NgModule, mediante el campo
providersdel decorador@NgModuleEn la raíz de la aplicación, agregando
providedIn: 'root'a un decorador@Injectable(como se mostró anteriormente)
Inyectar una dependencia en Angular es tan sencillo como declararla en el constructor de una clase. En el ejemplo de código anterior, la siguiente línea muestra cómo hacerlo:
NestJS
NestJS es un framework de JavaScript cuya arquitectura es eficaz para crear aplicaciones backend altamente eficientes, confiables y escalables. Como NestJS se inspira mucho en Angular para su diseño, la inyección de dependencias funciona de forma muy similar:
Ten en cuenta que, en NestJS y Angular, las dependencias suelen tratarse como singletons. Esto significa que, una vez encontrada una dependencia, su valor se almacena en caché y se reutiliza durante el ciclo de vida de la aplicación. Para cambiar este comportamiento en NestJS, debes configurar la propiedad `scope` en las opciones del decorador @Injectable. En Angular, debes configurar la propiedad `providedIn` en las opciones del decorador @Injectable.
Vue.js
Vue.js es un framework de JavaScript declarativo y basado en componentes para crear interfaces de usuario. La inyección de dependencias en Vue.js se puede configurar con las opciones `provide` y `inject` al crear un componente.
Para especificar qué datos estarán disponibles para los componentes descendientes, usa la opción provide:
Luego, puedes inyectar estas dependencias en los componentes que las necesiten mediante la opción inject :
Para proporcionar una dependencia a nivel de aplicación, de forma similar a providedIn: 'root' en Angular, usa el método provide de la instancia de la aplicación de Vue.js:
Frameworks de inyección de dependencias para JavaScript
Hasta ahora, has visto la inyección de dependencias en el contexto de un framework de JavaScript. Pero ¿qué pasa si alguien quiere iniciar un proyecto sin usar ninguno de los frameworks mencionados y no quiere implementar un contenedor o resolver de inyección de dependencias desde cero?
Hay algunas bibliotecas que ofrecen funciones de inyección de dependencias para este caso, como injection-js, InversifyJS y TSyringe. En la siguiente sección, nos centraremos en InversifyJS, pero quizás quieras revisar los otros paquetes para ver si se ajustan mejor a tus necesidades.
InversifyJS
InversifyJS es un contenedor de inyección de dependencias para JavaScript. Está diseñado para agregar la menor sobrecarga posible en tiempo de ejecución y, a la vez, facilitar y fomentar buenas prácticas de programación orientada a objetos (OOP) e IoC. Puedes encontrar el repositorio del proyecto en GitHub.
Para usar InversifyJS en un proyecto, debes agregarlo a las dependencias del proyecto. Aquí, configurarás InversifyJS con el ejemplo de videojuegos que usamos en todo el artículo. Para empezar, crea un proyecto nuevo con npm init. Abre una terminal y ejecuta los siguientes comandos en orden:
A continuación, agrega inversify a las dependencias del proyecto con npm o Yarn:
InversifyJS depende de la Metadata Reflection API, así que debes instalar y usar el paquete reflect-metadata como polyfill.
A continuación, agrega el código de las clases NSSConsole y GameReader, primero creando dos archivos vacíos:
Después, agrega el siguiente código a cada archivo, respectivamente:
Por último, configura el proyecto para usar InversifyJS:
Puedes probar la nueva configuración ejecutando node index.mjs desde la raíz del proyecto.
Aunque es mínimo, este ejemplo puede servir como un buen punto de partida para la mayoría de los proyectos que quieran usar InversifyJS.
Conclusión
En este artículo, aprendiste sobre la inyección de dependencias en JavaScript, sus ventajas y desventajas, ejemplos en frameworks populares de JavaScript y cómo usarla en un proyecto de JavaScript puro.



