Inversion of Control и DI в Node

IoC переворачивает контроль: не модуль решает что подключить, а framework передаёт зависимости снаружи. В Node чаще всего реализуется через namespace-инъекцию или фабрики, без DI-контейнеров а-ля Spring.

Суть

Без IoC модуль сам решает что ему нужно:

// db.js
const config = require('./config');         // hardcoded
const logger = require('./logger');          // hardcoded
const pg = require('pg');
// ...

Проблемы: нельзя подменить в тестах, циклические зависимости, тяжело переиспользовать. С IoC:

// db.js
module.exports = ({ config, logger }) => {
  const pg = require('pg');
  // используем переданные config, logger
  return { query, close };
};

// application.js — composition root
const db = require('./db')({ config, logger });

Подходы в Node.js

  1. Factory с параметрами — модуль экспортирует функцию, принимающую зависимости. Самый простой и идиоматичный для Node способ
  2. Namespace injection (Metarhia) — приложение собирает все слои в один объект app, передаваемый в каждый модуль через vm.createContext
  3. DI-контейнер (NestJS, tsyringe, awilix) — декораторы + reflect-metadata; ближе к Spring/Angular
  4. AsyncLocalStorage — изолированный context на async chain, удобно для request-scoped зависимостей

Пример namespace injection

// framework.js
const app = {
  config: require('./config'),
  log: require('./log')(...),
  db: null, // заполнится позже
  domain: {},
};
app.db = require('./db')(app);
app.domain.users = require('./domain/users')(app);
// каждый модуль получает весь app, использует только нужное

Подводные камни

  • Слишком много DI — если у каждой чистой утилиты 5 инъекций, читать тяжелее
  • Циклы: domain.users тянет domain.orders, тот — обратно. Лечится lazy-инициализацией или вынесением общего в третий модуль
  • Тестирование: главная польза IoC — подменить БД на мок в юнит-тестах
  • DI-контейнер ≠ архитектура: подключить awilix не делает приложение «чистым», важна декомпозиция

🎓 Источники

  • 🎓 [Inversion of Control and Dependency Injection in Node.js] · 2018-10-09 · YouTube · [Marp](../../../Documents/TimurShemsedinov/2018-10-09 — Inversion of Control and Dependency Injection in Node.js (Fz86Fdjz-LM).md)
    • Тезисы: IoC = framework вызывает приложение, не наоборот; DI как инструмент IoC; разные способы реализации в JS — фабрики, классы с конструктором, контейнеры, namespace
  • 🎓 [Песочницы, IoC, DI, IPC (Летняя школа 2017)] · 2019-11-30 · YouTube · [Marp](../../../Documents/TimurShemsedinov/2019-11-30 — 9. Летняя школа 2017 - Песочницы, IoC, DI, IPC, структура приложений (fjAUssuzTm4).md)
    • Тезисы: «приложение как в матрице» — framework решает что доступно; инжект API в песочницу; namespace вместо глобальных переменных; SandboxedFS запирает в каталоге через bind на путь + защита от выхода
    • Цитата: «Framework решает что доступно application — fs убрали, net оставили»

См. также