Ответ
Принцип инверсии зависимостей (DIP) гласит, что модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций. В Node.js это часто реализуется через внедрение зависимостей (Dependency Injection).
Проблема (нарушение DIP):
// Модуль верхнего уровня жестко зависит от конкретной реализации
const AWS = require('aws-sdk');
class ReportService {
constructor() {
// Прямое создание зависимости
this.s3 = new AWS.S3();
}
async saveReport(data) {
await this.s3.upload({ Bucket: 'my-bucket', Key: 'report.json', Body: data }).promise();
}
}
// Тестировать или изменить хранилище сложно.
Решение (соблюдение DIP):
// 1. Абстракция (интерфейс/контракт)
class StorageProvider {
async save(fileName, data) {
throw new Error('save() method not implemented');
}
}
// 2. Конкретные реализации зависят от абстракции
class S3Storage extends StorageProvider {
constructor(s3Client) {
super();
this.client = s3Client;
}
async save(fileName, data) {
const params = { Bucket: process.env.BUCKET_NAME, Key: fileName, Body: data };
return this.client.upload(params).promise();
}
}
class FileSystemStorage extends StorageProvider {
constructor(fsModule) {
super();
this.fs = fsModule.promises;
}
async save(fileName, data) {
return this.fs.writeFile(`./uploads/${fileName}`, data);
}
}
// 3. Модуль верхнего уровня зависит от абстракции
class ReportService {
// Зависимость внедряется извне
constructor(storageProvider) {
this.storage = storageProvider;
}
async generateAndSave() {
const reportData = JSON.stringify({ report: 'data' });
// Используется абстракция, а не конкретный S3 или FS
await this.storage.save('report.json', reportData);
}
}
// 4. Компоновка зависимостей (например, в корне приложения или контейнере DI)
const { S3 } = require('aws-sdk');
const s3Client = new S3();
const storage = new S3Storage(s3Client); // Или new FileSystemStorage(require('fs'))
const reportService = new ReportService(storage);
// Теперь ReportService не знает и не зависит от деталей хранения.
// Легко тестировать с моком StorageProvider и менять реализацию.