Что такое dependency injection

Ответ

Dependency Injection (DI, внедрение зависимостей) — это паттерн, при котором зависимости объекта (сервисы, репозитории, конфигурации) предоставляются ему извне, обычно через конструктор, метод или свойство, а не создаются внутри самого объекта.

Проблема без DI (жесткая связь):

class UserService {
  constructor() {
    // Зависимость создается внутри класса, что усложняет тестирование и замену.
    this.userRepository = new PostgreSQLUserRepository();
  }
  async getUser(id) {
    return this.userRepository.findById(id);
  }
}

Решение с DI (гибкая связь):

class UserService {
  // Зависимость внедряется извне.
  constructor(userRepository) {
    this.userRepository = userRepository;
  }
  async getUser(id) {
    return this.userRepository.findById(id);
  }
}

// Где-то в точке входа в приложение (например, index.js или контейнере DI) собираем объекты:
const userRepository = new PostgreSQLUserRepository();
const userService = new UserService(userRepository);

// Теперь для тестов легко подменить репозиторий на мок:
const mockRepository = { findById: jest.fn(() => ({ id: 1, name: 'Test' })) };
const testUserService = new UserService(mockRepository);

В Node.js-экосистеме DI часто используется с:

  • Фреймворками: NestJS имеет мощную встроенную систему DI на основе декораторов.
  • Библиотеками: InversifyJS, Awilix, TSyringe.
  • Ручной инъекцией: Простая передача зависимостей через конструкторы, что часто достаточно для небольших проектов.

Основные преимущества: повышение тестируемости, уменьшение связанности кода, упрощение рефакторинга и конфигурации.

Ответ 18+ 🔞

Э, слушай, смотри, тут про такую штуку — Dependency Injection, или по-нашему «внедрение зависимостей». Это, блядь, не какая-то космическая магия, а просто способ не устраивать внутри класса бардак на ровном месте.

Вот представь, чувак, у тебя есть какой-нибудь UserService. И он, такой весь важный, внутри себя сам создаёт себе базу данных, как ему вздумается. Получается пиздопроебибна архитектура, потому что он намертво прикручен к одной конкретной базе. Хочешь протестировать? Хуй там! Хочешь заменить базу на другую? Придётся лезть в кишки класса и всё переписывать. Доверия ебать ноль к такому коду.

class UserService {
  constructor() {
    // Вот эта хуйня — проблема. Класс сам себе хозяйник, сам создаёт зависимость.
    this.userRepository = new PostgreSQLUserRepository();
  }
}

А теперь смотри, как надо делать по-человечески. Мы говорим: «Слушай, UserService, ты не самодур. Какое тебе, сука, дело до того, откуда берётся репозиторий? Мы тебе его принесём, в рот мне чих-пых, с блюдечка. Ты просто работай».

class UserService {
  // Всё! Зависимость не создаётся тут, её ТЕБЕ ПЕРЕДАЮТ. Как в хорошем ресторане.
  constructor(userRepository) {
    this.userRepository = userRepository;
  }
}

И собирается это всё где-то наверху, в одном месте, которое знает всю кухню:

// Вот тут, в главном файле, мы как шеф-повара: готовим ингредиенты...
const userRepository = new PostgreSQLUserRepository();
// ...и засовываем их туда, куда надо.
const userService = new UserService(userRepository);

А главная ебёная фишка в чём? В тестах! Ты же не будешь ради одного юнит-теста поднимать целую базу данных? Это ж ядерена вошь! Вместо этого ты просто подсовываешь классу левую, фейковую зависимость, и он даже не заметит подмены.

// Подсовываем муляж, который делает что нам надо.
const mockRepository = { findById: jest.fn(() => ({ id: 1, name: 'Test' })) };
const testUserService = new UserService(mockRepository);
// Всё, тестируй на здоровье. Никаких лишних движений.

В мире Node.js под это дело есть целые овердохуища инструментов:

  • NestJS — там это вообще из коробки, с декораторами, красота.
  • Библиотеки типа InversifyJS или Awilix — для тех, кто любит покруче.
  • Ручная сборка — а для мелких проектов и этого хватит, зачем городить огород.

Итог, блядь: код становится в разы гибче, тестируется за секунды, и в нём не надо ковыряться с риском всё сломать. Просто э бошка думай наперёд, как компоненты друг с другом общаются.