No soy un gran admirador de frameworks grandes como NestJS; Siempre me ha gustado la libertad de construir mi software como quiero con la estructura que decido de forma liviana. Pero algo que me gustó cuando probé NestJS fue la inyección de dependencia.
La inyección de dependencia (DI) es un patrón de diseño que nos permite desarrollar código débilmente acoplado al eliminar la responsabilidad de crear y administrar dependencias de nuestras clases. Este patrón es crucial para escribir aplicaciones mantenibles, comprobables y escalables. En el ecosistema TypeScript, TSyringe se destaca como un contenedor de inyección de dependencias potente y liviano que simplifica este proceso.
TSyringe es un contenedor de inyección de dependencias liviano para aplicaciones TypeScript/JavaScript. Mantenido por Microsoft en su GitHub (https://github.com/microsoft/tsyringe), utiliza decoradores para realizar la inyección de Constructor. Luego, utiliza un contenedor de Inversión de Control para almacenar las dependencias en función de un token que puedes intercambiar por una instancia o un valor.
Antes de sumergirnos en TSyringe, exploremos brevemente qué es la inyección de dependencia y por qué es importante.
La inyección de dependencia es una técnica en la que un objeto recibe sus dependencias de fuentes externas en lugar de crearlas él mismo. Este enfoque ofrece varios beneficios:
Primero, configuremos TSyringe en su proyecto TypeScript:
npm install tsyringe reflect-metadata
En tu tsconfig.json, asegúrate de tener las siguientes opciones:
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } }
Importa metadatos reflejados en el punto de entrada de tu aplicación:
import "reflect-metadata";
El punto de entrada de su aplicación es, por ejemplo, el diseño raíz en Next.js 13, o puede ser el archivo principal en una pequeña aplicación Express.
Tomemos el ejemplo de la introducción y agreguemos el azúcar TSyringe:
Comencemos con el adaptador.
// @/adapters/userAdapter.ts import { injectable } from "tsyringe" @injectable() class UserAdapter { constructor(...) {...} async fetchByUUID(uuid) {...} }
¿Observas el decorador @injectable()? Es para decirle a TSyringe que esta clase se puede inyectar en tiempo de ejecución.
Entonces mi servicio está usando el adaptador que acabamos de crear. Inyectemos ese Adaptador en mi Servicio.
// @/core/user/user.service.ts import { injectable, inject } from "tsyringe" ... @injectable() class UserService { constructor(@inject('UserAdapter') private readonly userAdapter: UserAdapter) {} async fetchByUUID(uuid: string) { ... const { data, error } = await this.userAdapter.fetchByUUID(uuid); ... } }
Aquí también utilicé el decorador @injectable porque el Servicio se inyectará en mi clase de comando, pero también agregué el decorador @inject en los parámetros del constructor. Este decorador le dice a TSyringe que proporcione la instancia o el valor que tiene para el token UserAdapter para la propiedad userAdapter en tiempo de ejecución.
Y por último, pero no menos importante, la raíz de mi Core: la clase de comando (a menudo llamada erróneamente caso de uso).
// @/core/user/user.commands.ts import { inject } from "tsyringe" ... @injectable() class UserCommands { constructor(@inject('UserService') private readonly userService: UserService) {} async fetchByUUID(uuid) { ... const { data, error } = this.userService.fetchByUUID(uuid); ... } }
En este punto, le hemos dicho a TSyringe qué se va a inyectar y qué inyectar en el constructor. Pero todavía no hemos creado nuestros contenedores para almacenar las dependencias. Podemos hacerlo de dos maneras:
Podemos crear un archivo con nuestro registro de inyección de dependencia:
// @/core/user/user.dependencies.ts import { container } from "tsyringe" ... container.register("UserService", {useClass: UserService}) // associate the UserService with the token "UserService" container.register("UserAdapter", {useClass: UserAdapter}) // associate the UserAdapter with the token "UserAdapter" export { container }
Pero también podemos usar el decorador @registry.
// @/core/user/user.commands.ts import { inject, registry, injectable } from "tsyringe" ... @injectable() @registry([ { token: 'UserService', useClass: UserService }, { token: 'UserAdapter', useClass: UserAdapter }, ]) export class UserCommands { constructor(@inject('UserService') private readonly userService: UserService) {} async fetchByUUID(uuid) { ... const { data, error } = this.userService.fetchByUUID(uuid); ... } } container.register("UserCommands", { useClass: UserCommands}) export { container }
Ambos métodos tienen pros y contras, pero al final del día, es una cuestión de gustos.
Ahora que nuestro contenedor está lleno con nuestras dependencias, podemos obtenerlas del contenedor según sea necesario usando el método de resolución del contenedor.
import { container, UserCommands } from "@/core/user/user.commands" ... const userCommands = container.resolve("UserCommands") await userCommands.fetchByUUID(uuid) ...
Este ejemplo es bastante simple ya que cada clase solo depende de otra, pero nuestros servicios podrían depender de muchas, y la inyección de dependencia realmente ayudaría a mantener todo ordenado.
¡Pero espera! ¡No me dejes así! ¿Qué tal las pruebas?
Nuestras inyecciones también pueden ayudarnos a probar nuestro código enviando objetos simulados directamente a nuestras dependencias. Veamos un ejemplo de código:
import { container, UserCommands } from "@/core/user/user.commands" describe("test ftw", () => { let userAdapterMock: UserAdapterMock let userCommands: UserCommands beforeEach(() => { userAdapterMock = new UserAdapter() container.registerInstance("UserAdapter", userAdapter) userCommands = container.resolve ("UserCommands") }); ... });
Ahora el token UserAdapter contiene un simulacro que se inyectará en las clases dependientes.
Utilice tokens para nombrar: en lugar de utilizar cadenas literales para tokens de inyección, cree tokens constantes:
export const USER_REPOSITORY_TOKEN = Symbol("UserRepository");
Contenedores con alcance: use contenedores con alcance para dependencias con alcance de solicitud en aplicaciones web.
No abuses de DI: No es necesario inyectar todo. Utilice DI para inquietudes transversales y dependencias configurables.
Si has llegado hasta aquí, quiero agradecerte por leer. Espero que hayas encontrado este artículo instructivo. Recuerde considerar siempre las necesidades específicas de su proyecto al implementar la inyección de dependencias y los patrones arquitectónicos.
Los me gusta y los comentarios son las mejores formas de mejorar.
¡Feliz codificación!
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3