inversify для TypeScript: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое Inversify и зачем он нужен?

Inversify — это мощный и легковесный IoC (Inversion of Control) контейнер для TypeScript и JavaScript. Он реализует паттерн Dependency Injection (DI), который является одним из ключевых принципов SOLID. Inversify позволяет управлять зависимостями между классами, делая код более модульным, тестируемым и поддерживаемым.

Зачем это нужно? В традиционном программировании классы сами создают свои зависимости (например, через new). Это приводит к сильной связанности кода. Inversify переворачивает этот процесс: зависимости "внедряются" извне (отсюда и название — Inversion of Control). Контейнер берет на себя создание и управление жизненным циклом объектов.

Основные преимущества использования Inversify:

  • Слабая связанность: Классы зависят от абстракций (интерфейсов), а не от конкретных реализаций.
  • Легкое тестирование: Вы можете легко подменять реальные зависимости на моки/стабы в тестах.
  • Управление жизненным циклом: Контейнер может создавать объекты как синглтоны, так и каждый раз новые экземпляры.
  • Читаемость и поддерживаемость: Все зависимости явно объявлены и легко отслеживаются.
  • Масштабируемость: Отлично подходит для больших проектов с множеством модулей.

Установка Inversify

Inversify устанавливается через npm. Для работы с TypeScript также потребуется установить reflect-metadata (библиотека для работы с декораторами) и настроить tsconfig.json.

# Установка Inversify и reflect-metadata
npm install inversify reflect-metadata --save

# Установка типов для TypeScript (если не установлены)
npm install @types/node --save-dev

Настройка tsconfig.json:

{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "types": ["reflect-metadata"]
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Важно: Декораторы @injectable() и @inject() работают только при включенных experimentalDecorators и emitDecoratorMetadata.

Быстрый старт с примером кода

Создадим простой пример: сервис для отправки уведомлений. У нас будет интерфейс INotificationService, его реализация EmailNotificationService и класс UserService, который использует этот сервис.

// 1. Импортируем необходимые модули
import "reflect-metadata";
import { Container, injectable, inject } from "inversify";

// 2. Определяем интерфейс (абстракцию)
interface INotificationService {
  send(message: string): void;
}

// 3. Реализация интерфейса
@injectable()
class EmailNotificationService implements INotificationService {
  send(message: string): void {
    console.log(`Отправка email: ${message}`);
  }
}

// 4. Класс, который зависит от INotificationService
@injectable()
class UserService {
  private notificationService: INotificationService;

  constructor(
    @inject("INotificationService") notificationService: INotificationService
  ) {
    this.notificationService = notificationService;
  }

  notifyUser(userId: string): void {
    this.notificationService.send(`Уведомление для пользователя ${userId}`);
  }
}

// 5. Создаем контейнер и регистрируем зависимости
const container = new Container();
container.bind("INotificationService").to(EmailNotificationService);
container.bind(UserService).toSelf();

// 6. Получаем экземпляр UserService из контейнера
const userService = container.get(UserService);
userService.notifyUser("12345"); // Вывод: Отправка email: Уведомление для пользователя 12345

Основные методы/функции Inversify

1. Container — Конструктор контейнера

Что делает: Создает новый экземпляр IoC контейнера. Контейнер — это центральное хранилище всех зарегистрированных зависимостей.

Параметры: Принимает опциональный объект ContainerOptions (например, defaultScope для установки скоупа по умолчанию).

Пример:

import { Container } from "inversify";

const container = new Container();
// Или с настройками:
const containerWithOptions = new Container({ defaultScope: "Singleton" });

2. container.bind() — Регистрация зависимости

Что делает: Регистрирует привязку (binding) между идентификатором (символом, строкой или классом) и реализацией.

Параметры: Принимает идентификатор (ServiceIdentifier). Возвращает объект BindingToSyntax для дальнейшей настройки.

Пример:

container.bind<INotificationService>("INotificationService").to(EmailNotificationService);
container.bind<UserService>(UserService).toSelf();

3. container.get() — Получение экземпляра

Что делает: Разрешает зависимость и возвращает экземпляр зарегистрированного класса.

Параметры: Принимает идентификатор (ServiceIdentifier).

Пример:

const userService = container.get<UserService>(UserService);
const notificationService = container.get<INotificationService>("INotificationService");

4. container.getAll() — Получение всех экземпляров

Что делает: Возвращает массив всех зарегистрированных экземпляров для указанного идентификатора. Полезно, когда зарегистрировано несколько реализаций одного интерфейса.

Параметры: Принимает идентификатор (ServiceIdentifier).

Пример:

// Регистрируем несколько реализаций
container.bind<INotificationService>("INotificationService").to(EmailNotificationService);
container.bind<INotificationService>("INotificationService").to(SmsNotificationService);

// Получаем все
const services = container.getAll<INotificationService>("INotificationService");
services.forEach(service => service.send("Тестовое сообщение"));

5. container.isBound() — Проверка регистрации

Что делает: Проверяет, зарегистрирована ли зависимость с указанным идентификатором.

Параметры: Принимает идентификатор (ServiceIdentifier). Возвращает boolean.

Пример:

if (container.isBound("INotificationService")) {
  const service = container.get<INotificationService>("INotificationService");
  service.send("Проверка пройдена");
}

6. container.unbind() — Удаление регистрации

Что делает: Удаляет все привязки для указанного идентификатора.

Параметры: Принимает идентификатор (ServiceIdentifier).

Пример:

container.unbind("INotificationService");
// Теперь container.isBound("INotificationService") вернет false

7. container.rebind() — Перерегистрация зависимости

Что делает: Удаляет все существующие привязки для идентификатора и создает новую. Удобно для переопределения зависимостей (например, в тестах).

Параметры: Принимает идентификатор (ServiceIdentifier). Возвращает BindingToSyntax.

Пример:

// Переопределяем реализацию
container.rebind<INotificationService>("INotificationService").to(MockNotificationService);

8. container.snapshot() и container.restore() — Снимки состояния

Что делает: snapshot() сохраняет текущее состояние контейнера (все привязки). restore() восстанавливает последнее сохраненное состояние. Полезно для тестирования.

Параметры: Нет.

Пример:

// Сохраняем состояние
container.snapshot();

// Вносим изменения для теста
container.rebind<INotificationService>("INotificationService").to(MockNotificationService);

// После теста восстанавливаем
container.restore();

9. @injectable() — Декоратор класса

Что делает: Помечает класс как "инжектируемый". Без этого декоратора Inversify не сможет управлять зависимостями класса.

Параметры: Нет (применяется к классу).

Пример:

@injectable()
class MyService {
  // ...
}

10. @inject() — Декоратор параметра

Что делает: Указывает, какую зависимость нужно внедрить в параметр конструктора или свойства класса.

Параметры: Принимает идентификатор (ServiceIdentifier).

Пример:

@injectable()
class UserService {
  constructor(
    @inject("INotificationService") private notificationService: INotificationService
  ) {}
}

11. container.applyMiddleware() — Применение middleware

Что делает: Позволяет добавить middleware (промежуточное ПО) для перехвата и модификации процесса разрешения зависимостей.

Параметры: Принимает одну или несколько функций middleware.

Пример:

// Middleware для логирования
const loggingMiddleware: Middleware = (planAndResolve) => {
  return (args) => {
    console.log(`Разрешение зависимости: ${args.serviceIdentifier}`);
    return planAndResolve(args);
  };
};

container.applyMiddleware(loggingMiddleware);

Реальный практический пример использования

Рассмотрим более сложный пример: приложение для управления задачами (Task Manager). У нас будут сервисы для работы с базой данных, логирования и уведомлений.

import "reflect-metadata";
import { Container, injectable, inject } from "inversify";

// 1. Определяем интерфейсы
interface ILogger {
  log(message: string): void;
}

interface IDatabase {
  save(task: string): void;
  getAll(): string[];
}

interface INotificationService {
  send(message: string): void;
}

// 2. Реализации
@injectable()
class ConsoleLogger implements ILogger {
  log(message: string): void {
    console.log(`[LOG] ${new Date().toISOString()}: ${message}`);
  }
}

@injectable()
class InMemoryDatabase implements IDatabase {
  private tasks: string[] = [];

  save(task: string): void {
    this.tasks.push(task);
  }

  getAll(): string[] {
    return this.tasks;
  }
}

@injectable()
class EmailNotificationService implements INotificationService {
  send(message: string): void {
    console.log(`[EMAIL] ${message}`);
  }
}

// 3. Основной сервис управления задачами
@injectable()
class TaskManager {
  constructor(
    @inject("ILogger") private logger:                    

Рекомендаций