Inyección de Dependencias
Guía de Inyección de Dependencias
Sección titulada «Guía de Inyección de Dependencias»Aprende a usar el sistema de inyección de dependencias integrado en Veloce de manera efectiva.
Tabla de Contenidos
Sección titulada «Tabla de Contenidos»- Introducción
- Uso Básico
- Alcances de Dependencias
- Registrando Dependencias
- Patrones Avanzados
- Pruebas con DI
- Mejores Prácticas
Introducción
Sección titulada «Introducción»La Inyección de Dependencias (DI) es un patrón de diseño que te ayuda a escribir código modular, testeable y mantenible. Veloce incluye un contenedor DI integrado que gestiona el ciclo de vida de tus dependencias.
Beneficios
Sección titulada «Beneficios»- Testeabilidad: Fácil de simular dependencias en pruebas
- Modularidad: Clara separación de responsabilidades
- Mantenibilidad: Los cambios en dependencias no afectan a los consumidores
- Flexibilidad: Fácil de intercambiar implementaciones
Uso Básico
Sección titulada «Uso Básico»Definiendo un Servicio
Sección titulada «Definiendo un Servicio»class DatabaseService { async query(sql: string) { // Lógica de base de datos return []; }}
class UserService { async getUsers() { return [ { id: 1, name: 'John' }, { id: 2, name: 'Jane' }, ]; }
async getUserById(id: string) { const users = await this.getUsers(); return users.find(u => u.id === parseInt(id)); }}Inyectando Dependencias
Sección titulada «Inyectando Dependencias»Usa el decorador @Depends() para inyectar dependencias en tus manejadores:
@Controller('/users')class UserController { @Get('/') async listUsers(@Depends(UserService) userService: UserService) { return await userService.getUsers(); }
@Get('/:id') async getUser( @Param('id') id: string, @Depends(UserService) userService: UserService ) { const user = await userService.getUserById(id); if (!user) { throw new HTTPException(404, 'Usuario no encontrado'); } return user; }}Registrando Servicios
Sección titulada «Registrando Servicios»Registra tus servicios con el contenedor DI:
const app = new Veloce();const container = app.getContainer();
// Registrar servicioscontainer.register(DatabaseService, { scope: 'singleton' });container.register(UserService, { scope: 'request' });
// Incluir controladorapp.include(UserController);Alcances de Dependencias
Sección titulada «Alcances de Dependencias»Veloce soporta tres alcances de dependencias:
Singleton
Sección titulada «Singleton»Una instancia para todo el ciclo de vida de la aplicación.
container.register(DatabaseService, { scope: 'singleton' });Usar para:
- Conexiones de base de datos
- Servicios de configuración
- Servicios sin estado
- Cachés
Request
Sección titulada «Request»Una instancia por petición HTTP (por defecto).
container.register(UserService, { scope: 'request' });Usar para:
- Servicios específicos de petición
- Servicios que necesitan contexto de petición
- Servicios con estado de petición
Transient
Sección titulada «Transient»Nueva instancia cada vez que se inyecta.
container.register(TemporaryService, { scope: 'transient' });Usar para:
- Objetos con estado
- Objetos que no deben compartirse
- Objetos ligeros
Registrando Dependencias
Sección titulada «Registrando Dependencias»Registro de Clase
Sección titulada «Registro de Clase»class MyService { doSomething() { return 'hecho'; }}
container.register(MyService, { scope: 'singleton' });Registro con Factory
Sección titulada «Registro con Factory»Usa una función factory para inicialización compleja:
class DatabaseService { constructor(private connectionString: string) {}
async query(sql: string) { // Usar connectionString return []; }}
container.register(DatabaseService, { scope: 'singleton', factory: () => { const connectionString = process.env.DATABASE_URL || 'default'; return new DatabaseService(connectionString); },});Factory Asíncrono
Sección titulada «Factory Asíncrono»container.register(DatabaseService, { scope: 'singleton', factory: async () => { const config = await loadConfig(); const db = new DatabaseService(config.dbUrl); await db.connect(); return db; },});Patrones Avanzados
Sección titulada «Patrones Avanzados»Dependencias Anidadas
Sección titulada «Dependencias Anidadas»Los servicios pueden depender de otros servicios:
class DatabaseService { async query(sql: string) { return []; }}
class UserRepository { constructor(private db: DatabaseService) {}
async findAll() { return this.db.query('SELECT * FROM users'); }}
class UserService { constructor(private userRepo: UserRepository) {}
async getUsers() { return this.userRepo.findAll(); }}
// Registrar todos los servicioscontainer.register(DatabaseService, { scope: 'singleton' });container.register(UserRepository, { scope: 'singleton' });container.register(UserService, { scope: 'request' });Múltiples Dependencias
Sección titulada «Múltiples Dependencias»Inyecta múltiples dependencias en un solo manejador:
@Post('/users')async createUser( @Body(UserSchema) userData: User, @Depends(UserService) userService: UserService, @Depends(EmailService) emailService: EmailService, @Depends(LoggerService) logger: LoggerService) { logger.info('Creando usuario', userData);
const user = await userService.createUser(userData);
await emailService.sendWelcomeEmail(user.email);
logger.info('Usuario creado', user);
return user;}Pruebas con DI
Sección titulada «Pruebas con DI»Simulando Dependencias
Sección titulada «Simulando Dependencias»import { describe, it, expect, beforeEach } from 'bun:test';import { createTestApp, mockDependency } from 'veloce/testing';
describe('UserController', () => { let app: Veloce; let mockUserService: any;
beforeEach(() => { app = createTestApp();
// Crear mock mockUserService = { getUsers: async () => [ { id: 1, name: 'Usuario de Prueba' }, ], getUserById: async (id: string) => { return { id: parseInt(id), name: 'Usuario de Prueba' }; }, };
// Reemplazar servicio real con mock mockDependency(UserService, mockUserService);
app.include(UserController); });
it('debería obtener usuarios', async () => { const response = await app.request('/users'); const data = await response.json();
expect(data).toEqual([ { id: 1, name: 'Usuario de Prueba' }, ]); });});Mejores Prácticas
Sección titulada «Mejores Prácticas»1. Usa Inyección por Constructor para Servicios
Sección titulada «1. Usa Inyección por Constructor para Servicios»// ✓ Buenoclass UserService { constructor( private db: DatabaseService, private cache: CacheService ) {}
async getUsers() { const cached = await this.cache.get('users'); if (cached) return cached;
const users = await this.db.query('SELECT * FROM users'); await this.cache.set('users', users); return users; }}
// ✗ Malo - Instanciación directaclass UserService { private db = new DatabaseService(); private cache = new CacheService();}2. Elige Alcances Apropiados
Sección titulada «2. Elige Alcances Apropiados»// ✓ Bueno - Servicios sin estado como singletoncontainer.register(ConfigService, { scope: 'singleton' });container.register(DatabaseService, { scope: 'singleton' });
// ✓ Bueno - Servicios específicos de petición como request-scopedcontainer.register(AuthService, { scope: 'request' });container.register(RequestLogger, { scope: 'request' });3. Evita Dependencias Circulares
Sección titulada «3. Evita Dependencias Circulares»// ✗ Malo - Dependencia circularclass ServiceA { constructor(private serviceB: ServiceB) {}}
class ServiceB { constructor(private serviceA: ServiceA) {}}
// ✓ Bueno - Extraer lógica compartidaclass SharedService { doSomething() {}}
class ServiceA { constructor(private shared: SharedService) {}}
class ServiceB { constructor(private shared: SharedService) {}}4. Mantén los Servicios Enfocados
Sección titulada «4. Mantén los Servicios Enfocados»// ✓ Bueno - Responsabilidad únicaclass UserRepository { async findAll() {} async findById(id: string) {} async create(user: User) {} async update(id: string, user: User) {} async delete(id: string) {}}
class UserService { constructor(private userRepo: UserRepository) {}
async getUsers() { return this.userRepo.findAll(); }
async validateAndCreateUser(userData: any) { // Lógica de validación return this.userRepo.create(userData); }}Próximos Pasos
Sección titulada «Próximos Pasos»- Aprende sobre Plugins
- Lee la Guía de Pruebas
- Revisa la Guía de Inicio