Ответ
В моих Node.js проектах DI решает несколько ключевых проблем:
1. Тестируемость — мокирование зависимостей становится тривиальным:
// Без DI - сложно тестировать
class UserService {
constructor() {
this.db = require('./database'); // Прямая зависимость
}
async getUser(id) {
return this.db.query('SELECT * FROM users WHERE id = ?', [id]);
}
}
// С DI - легко тестировать
class UserService {
constructor(db) {
this.db = db; // Зависимость инжектится
}
async getUser(id) {
return this.db.query('SELECT * FROM users WHERE id = ?', [id]);
}
}
// В тесте
const mockDb = {
query: jest.fn().mockResolvedValue({ id: 1, name: 'John' })
};
const service = new UserService(mockDb);
await service.getUser(1);
expect(mockDb.query).toHaveBeenCalled();
2. Гибкость конфигурации — можно переключать реализации без изменения кода:
// В development используем SQLite
if (process.env.NODE_ENV === 'development') {
container.register('db', new SQLiteDatabase());
}
// В production - PostgreSQL
else {
container.register('db', new PostgresDatabase(process.env.DATABASE_URL));
}
3. Управление жизненным циклом — DI-контейнеры вроде tsyringe или inversify позволяют контролировать создание инстансов:
import { singleton } from 'tsyringe';
@singleton() // Синглтон на всё приложение
class Logger {
log(message: string) {
console.log(`[${new Date().toISOString()}] ${message}`);
}
}
4. Избегание циклических зависимостей — контейнер разрешает зависимости лениво, что помогает с circular dependencies в Node.js.
5. Чистая архитектура — зависимости явно объявлены в конструкторе, что делает код более читаемым и поддерживаемым.