Saltearse al contenido

Inyección de Dependencias

Aprende a usar el sistema de inyección de dependencias integrado en Veloce de manera efectiva.

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.

  • 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
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));
}
}

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;
}
}

Registra tus servicios con el contenedor DI:

const app = new Veloce();
const container = app.getContainer();
// Registrar servicios
container.register(DatabaseService, { scope: 'singleton' });
container.register(UserService, { scope: 'request' });
// Incluir controlador
app.include(UserController);

Veloce soporta tres alcances de dependencias:

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

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

Nueva instancia cada vez que se inyecta.

container.register(TemporaryService, { scope: 'transient' });

Usar para:

  • Objetos con estado
  • Objetos que no deben compartirse
  • Objetos ligeros
class MyService {
doSomething() {
return 'hecho';
}
}
container.register(MyService, { scope: 'singleton' });

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);
},
});
container.register(DatabaseService, {
scope: 'singleton',
factory: async () => {
const config = await loadConfig();
const db = new DatabaseService(config.dbUrl);
await db.connect();
return db;
},
});

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 servicios
container.register(DatabaseService, { scope: 'singleton' });
container.register(UserRepository, { scope: 'singleton' });
container.register(UserService, { scope: 'request' });

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;
}
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' },
]);
});
});

1. Usa Inyección por Constructor para Servicios

Sección titulada «1. Usa Inyección por Constructor para Servicios»
// ✓ Bueno
class 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 directa
class UserService {
private db = new DatabaseService();
private cache = new CacheService();
}
// ✓ Bueno - Servicios sin estado como singleton
container.register(ConfigService, { scope: 'singleton' });
container.register(DatabaseService, { scope: 'singleton' });
// ✓ Bueno - Servicios específicos de petición como request-scoped
container.register(AuthService, { scope: 'request' });
container.register(RequestLogger, { scope: 'request' });
// ✗ Malo - Dependencia circular
class ServiceA {
constructor(private serviceB: ServiceB) {}
}
class ServiceB {
constructor(private serviceA: ServiceA) {}
}
// ✓ Bueno - Extraer lógica compartida
class SharedService {
doSomething() {}
}
class ServiceA {
constructor(private shared: SharedService) {}
}
class ServiceB {
constructor(private shared: SharedService) {}
}
// ✓ Bueno - Responsabilidad única
class 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);
}
}