Análisis en profundidad de la inyección de dependencias en NestJS: mecanismos subyacentes y ejemplos de aplicación

  • 870Palabras
  • 4Minutos
  • 08 Jul, 2024

La inyección de dependencias (Dependency Injection, DI) es un patrón de diseño que tiene como objetivo reducir el acoplamiento entre componentes al pasar las dependencias de estos como parámetros. NestJS, como un marco avanzado para Node.js, utiliza un enfoque fuertemente tipado y modular para implementar la inyección de dependencias. Este artículo explorará en profundidad el mecanismo de inyección de dependencias en NestJS y su aplicación práctica, partiendo desde los principios de implementación subyacentes.

1. Mecanismo de inyección de dependencias en NestJS

El mecanismo de inyección de dependencias en NestJS se basa en metadatos de reflexión y un gráfico de dependencias. Utiliza decoradores y la API de reflexión de TypeScript para resolver automáticamente las dependencias de las clases y realizar la inyección según el gráfico de dependencias.

1.1 Reflexión y metadatos

NestJS usa la biblioteca reflect-metadata para recopilar y leer los metadatos de reflexión. En TypeScript, se pueden usar decoradores como @Injectable() y @Inject() para marcar clases y propiedades, permitiendo que NestJS recoja estos metadatos durante la compilación y los use para construir las relaciones de dependencias en tiempo de ejecución.

1
import "reflect-metadata";
2
3
@Injectable()
4
export class UsersService {
5
constructor(private readonly userRepository: UserRepository) {}
6
}

En el código anterior, el decorador @Injectable() marca la clase UsersService, permitiendo que NestJS reconozca esta clase como un servicio inyectable. En el constructor, userRepository se marca como private readonly, indicando a NestJS que esta clase depende de UserRepository.

1.2 Gráfico de dependencias y resolución

Al iniciar la aplicación, NestJS escanea todos los módulos, controladores y proveedores, recopila sus metadatos y construye un gráfico de dependencias. Este gráfico de dependencias es un gráfico acíclico dirigido (DAG) que representa las relaciones de dependencia entre clases. NestJS utiliza este gráfico para resolver dependencias e instanciar objetos según sea necesario.

1
@Module({
2
providers: [UsersService, UserRepository],
3
})
4
export class UsersModule {}

Los proveedores declarados en un módulo se registran en el gráfico de dependencias de NestJS. Luego, NestJS resuelve las dependencias de estos proveedores y crea instancias. Durante este proceso, NestJS sigue un flujo típico de “solicitud-instanciación-inyección”:

  1. Solicitud: La aplicación solicita un servicio (por ejemplo, UsersService).
  2. Instanciación: NestJS verifica los parámetros del constructor del servicio, busca todas las dependencias e instancia estas dependencias de manera recursiva.
  3. Inyección: Una vez instanciado, NestJS inyecta las dependencias en el servicio y devuelve la instancia del servicio.

2. Aplicación práctica: módulos, controladores, servicios y proveedores personalizados

2.1 Configuración de módulos

Los módulos son unidades de organización en NestJS. Cada módulo se define con el decorador @Module(), que incluye proveedores, controladores y otros módulos que se puedan importar.

users.module.ts
1
import { Module } from "@nestjs/common";
2
import { UsersService } from "./users.service";
3
import { UsersController } from "./users.controller";
4
import { UserRepository } from "./user.repository";
5
6
@Module({
7
providers: [UsersService, UserRepository], // Registro de proveedores
8
controllers: [UsersController], // Registro de controladores
9
})
10
export class UsersModule {}

En el ejemplo anterior, UsersModule registra UsersService y UserRepository como proveedores, y declara UsersController.

2.2 Configuración de controladores

Los controladores manejan solicitudes HTTP y retornan respuestas. Una clase de controlador se define con el decorador @Controller(), y los métodos se marcan como manejadores de rutas con decoradores como @Get() o @Post().

users.controller.ts
1
import { Controller, Get } from "@nestjs/common";
2
import { UsersService } from "./users.service";
3
4
@Controller("users")
5
export class UsersController {
6
constructor(private readonly usersService: UsersService) {}
7
8
@Get()
9
async findAll() {
10
return this.usersService.findAll();
11
}
12
}

En UsersController, se inyecta UsersService a través del constructor, permitiendo el uso de los métodos del servicio para manejar la lógica de negocio.

2.3 Configuración de servicios

Los servicios contienen la lógica de negocio de la aplicación y se proporcionan a controladores y otros servicios a través del sistema de inyección de dependencias.

users.service.ts
1
import { Injectable } from "@nestjs/common";
2
import { UserRepository } from "./user.repository";
3
4
@Injectable()
5
export class UsersService {
6
constructor(private readonly userRepository: UserRepository) {}
7
8
async findAll() {
9
return this.userRepository.findAll();
10
}
11
}

En UsersService, se inyecta UserRepository y se usa para la lógica de acceso a datos. El decorador @Injectable() permite que el servicio sea gestionado por el sistema de DI de NestJS.

2.4 Proveedores personalizados

Los proveedores personalizados permiten a los desarrolladores proporcionar dependencias de diferentes maneras (como useValue, useClass, useFactory). Por ejemplo, useFactory se puede utilizar para inicializaciones asíncronas.

async.service.ts
1
import { Injectable } from "@nestjs/common";
2
3
@Injectable()
4
export class AsyncService {
5
constructor(private readonly someDependency: any) {}
6
7
async doSomething() {
8
return "done";
9
}
10
}
11
12
// app.module.ts
13
import { Module } from "@nestjs/common";
14
import { AsyncService } from "./async.service";
15
16
@Module({
17
providers: [
18
{
19
provide: AsyncService,
20
useFactory: async () => {
21
const dependency = await someAsyncInitialization();
22
return new AsyncService(dependency);
23
},
24
},
25
],
26
})
27
export class AppModule {}
28
29
async function someAsyncInitialization() {
30
// Simulación de inicialización asíncrona
31
return new Promise((resolve) =>
32
setTimeout(() => resolve("initialized dependency"), 1000),
33
);
34
}

En el código anterior, AsyncService se inicializa de manera asíncrona mediante una función de fábrica useFactory. Este método es útil para dependencias que requieren configuración compleja o inicializaciones asíncronas.

Conclusión

El sistema de inyección de dependencias de NestJS, basado en las características fuertemente tipadas de TypeScript y los decoradores, implementa una gestión de dependencias flexible y potente a través de la reflexión y el gráfico de dependencias. No solo soporta la inyección básica de instancias de clases, sino que también ofrece opciones de configuración ricas para proveedores personalizados, permitiendo a los desarrolladores manejar fácilmente relaciones de dependencias complejas. Este diseño dota a las aplicaciones de NestJS de una alta modularidad, capacidad de prueba y mantenibilidad.