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
- Factory с параметрами — модуль экспортирует функцию, принимающую зависимости. Самый простой и идиоматичный для Node способ
- Namespace injection (Metarhia) — приложение собирает все слои в один объект
app, передаваемый в каждый модуль черезvm.createContext - DI-контейнер (NestJS, tsyringe, awilix) — декораторы + reflect-metadata; ближе к Spring/Angular
- 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 оставили»
См. также
- Inversion of Control — общая теория IoC (DI ⊂ IoC, event-driven, шаблонные методы, sandbox-based)
- Песочницы и VM в Node
- Структура Node.js проекта и слои
- Layered Architecture