Ещё одна имплементация Dependency Injection в JavaScript — с ES6-модулями, с возможностью использовать один и тот же код в браузере, и в nodejs и не использовать транспиляторы.   Под катом — мой взгляд на DI, его место в современных web-приложениях, принципиальная реализация DI-контейнера, способного создавать объекты и на фронте, и на бэке, а также объяснение, при чём тут Майкл Джексон. Очень сильно прошу тех, кому изложенное в статье покажется банальным, не насиловать себя и не читать до конца, чтобы потом, разочаровавшись, не ставить "минус". Я не против "минусов" — но только если минус сопровождается комментарием, что именно в публикации вызвало отрицательную реакцию. Это техническая статья, поэтому постарайтесь отнестись снисходительно к стилю изложения, а критиковать именно техническую составляющую изложенного. Спасибо. Я очень уважаю функциональное программирование, но большую часть своей профессиональной деятельности я посвятил созданию приложений, состоящих из объектов. JavaScript мне импонирует тем, что функции в нём также являются объектами. При создании приложений я мыслю объектами, это моя профессиональная деформация. По времени жизни объекты в приложении можно разделить на следующие категории: В связи с этим в программировании есть такие шаблоны проектирования, как: Т.е., с моей точки зрения, приложение состоит из постоянно-существующих одиночек, которые либо сами выполняют требуемые операции, либо для их выполнения порождают временные объекты. Внедрение зависимостей — это подход, который облегчает создание объектов в приложении. Т.е., в приложении существует специальный объект, который "знает", каким образом создавать все остальные объекты. Такой объект называется Контейнер Объектов (иногда — Менеджер Объектов). Контейнер Объектов не является Божественным Объектом, т.к. его задачей является только создание значимых объектов приложения и предоставление доступа к ним другим объектам. Подавляющее большинство объектов приложения, будучи порождёнными Контейнером и размещаясь в нём, никакого представления о самом Контейнере не имеют. Их можно поместить в любую другую среду, снабдить необходимыми зависимостями и они будут также замечательно функционировать и там (тестировщики в курсе, о чём я). По большому счёту есть два способа внедрить зависимости в объект: Я, в основном, использовал первый подход, поэтому дальнейшее описание я буду вести с точки зрения внедрения зависимостей через конструктор. Допустим, что у нас есть приложение, состоящее из трёх объектов:  В PHP (этот язык с давними традициями DI у меня в данный момент находится в активном багаже, к JS я перейду чуть позже) подобная ситуация могла бы быть отражена таким образом: Этой информации должно хватать, чтобы DI-контейнер (например, league/container) при соответствующей настройке смог по запросу на создание объекта Application также создать его зависимости Service и Config и передать их параметрами в конструктор объекта Application. Каким же образом Контейнер объектов понимает, что конструктору объекта Application требуются два объекта Config и Service? Путём анализа объекта через Reflection API (Java, PHP) или через анализ непосредственно кода объекта (аннотаций к коду). То есть, в общем случае, мы можем определить имена переменных, которые ожидает увидеть на входе конструктор объекта, а если язык типизируемый, то можем получить также и типы этих переменных.  Таким образом, в качестве идентификаторов объектов Контейнер может оперировать либо именами входных параметров конструктора, либо типами входных параметров. Объект может быть в явном виде создан программистом и помещён в Контейнер под соответствующим идентификатором (например, "configuration") а может быть создан Контейнером по некоторым определённым правилам. Эти правила, по большому счёту, сводятся к сопоставлению идентификатора объекта его коду. Правила можно задавать явно (маппинг в виде кода, XML, JSON, ...) или в виде некоторого алгоритма: В PHP составление правил сопоставления имени класса файлу с его исходным кодом стандартизированы (PSR-4), в Java сопоставление идёт на уровне конфигурации JVM (class loader). Если Контейнер предусматривает автоматический поиск исходников при создании объектов, то имена классов являются достаточно хорошими идентификаторами для объектов в таком Контейнере. Обычно в проекте, помимо собственного кода, используются также сторонние модули. С появлением менеджеров зависимостей (maven, composer, npm) использование модулей очень сильно упростилось, а количество модулей в проектах очень сильно увеличилось. Пространства имён позволяют существовать в едином проекте одноимённым элементам кода из различных модулей (классы, функции, константы). Есть языки, в которых пространство имён встроено изначально (Java): есть языки, в которых пространство имён добавлено в ходе развития языка (PHP): Хорошая реализация пространства имён позволяет однозначно адресовать любой элемент кода: Пространство имён решает задачу по упорядочиванию множества программных элементов в проекте, а файловая структура решает задачу по упорядочиванию файлов на диске. Поэтому между ними не просто много общего, а иногда и очень много — в Java, например, публичный класс в пространстве имён однозначно должен быть привязан к файлу с кодом этого класса. Таким образом, использование в Контейнере в качестве идентификаторов объектов идентификатора класса объекта в пространстве имён проекта является хорошей идеей и может служить основой для создания правил по автоматическому обнаружению исходных кодов при создании требуемого объекта. В PHP composer пространство имён модуля маппится на файловую систему внутри модуля в дескрипторе модуля composer.json: JS-сообщество могло бы делать аналогичный маппинг в package.json, если бы в JS были пространства имён. Выше я обозначил, что в качестве идентификаторов Контейнер может использовать либо имена входных параметров конструктора, либо типы входных параметров. Проблема в том, что: Разработчики DI-контейнера awilix предлагают использовать объект в качестве единственного входного параметра конструктора, а в качестве зависимостей — свойства этого объекта: Идентификатор свойства объекта в JS может состоять из буквенно-цифровых символов, "_" и "$", причем не может начинаться с цифры. Так как нам для автозагрузки нужно будет мапить идентификаторы зависимостей на путь к их исходникам в файловой системе, то лучше отказаться от использования "$" и воспользоваться опытом PHP. До появления оператора namespace в некоторых framework'ах (например, в Zend 1) использовали такие наименования для классов: Таким образом, мы могли бы отразить наше приложение из трёх объектов (Application, Config, Service) на JS как-то так: Если мы размещаем код каждого класса: в своём файле внутри модуля нашего проекта: То мы можем связать корневой каталог модуля с корневым "namespace'ом" модуля в конфигурации Контейнера: а затем, отталкиваясь от этой информации, конструировать на основании идентификатора зависимости (Vendor_Project_Config) путь к соответствующим исходникам (${module_root}/src/Config.js). ES6 предлагает общую конструкцию для загрузки ES6-модулей: Так как нам нужно один объект (класс) привязывать к одному файлу, то есть смысл в исходнике экспортировать этот класс по-умолчанию: В принципе, можно не писать такое длинное имя для класса, достаточно просто Something и тоже будет работать, но в Zend 1 писали и не переломились, а уникальность имени класса в пределах проекта положительно сказывается как на возможностях IDE (autocomplete и контекстные подсказки), так и при отладке:  Импорт класса и создание объекта в таком случае выглядит так: Импорт работает как в браузере, так и в nodejs, но есть нюансы. Например, браузер не понимает импорта nodejs-модулей: В браузере получаем ошибку: То есть, если мы хотим, чтобы наш код работал и в браузере, и в nodejs, мы не можем использовать конструкции которые не понимает браузер или nodejs. Я специально акцентирую на этом внимание, потому что такой вывод слишком естественен, чтобы о нём думать. Как дышать. Это сугубо моё личное мнение, обусловленное моим персональным опытом, как и всё остальное в этой публикации.  В web-приложениях JS практически безальтернативно занимает своё место на фронте, в браузере. На серверной стороне плотно окопались Java, PHP, .Net, Ruby, python,… Но с появлением nodejs JavaScript также проник и на сервер. А технологии, используемые в других языках, в том числе и DI, начали проникать в серверный JS. Развитие JavaScript обусловлено асинхроностью работы кода в браузере. Асинхронность не является исключительной особенностью JS, скорее врождённой. Сейчас наличие JS и на сервере, и на фронте уже никого не удивляет, а скорее, стимулирует к использованию одних и тех же подходов на обоих "концах" web-приложения. И одного и того же кода. Разумеется, что фронт и бэк слишком различаются по своей сути и по решаемым задачам, чтобы использовать один и тот же код и там, и там. Но можно предположить, что в более-менее сложном приложении будет код браузерный, серверный и общий. DI уже сейчас используется на фронте, в RequireJS: Правда тут идентификаторы зависимостей прописываются в явном виде и сразу в виде ссылок на исходники (можно настроить маппинг идентификаторов в конфиге загрузчика). В современных web-приложениях DI существует не только на серверной стороне, но и в браузере. При включении поддержки ES-модулей в nodejs (флаг --experimental-modules) движок идентифицирует содержимое файлов с расширением *.mjs как EcmaScript-модули (в отличие от Common-модулей с расширением *.cjs). Иногда такой подход называют "Michael Jackson Solution", а скрипты — Michael Jackson Scripts (*.mjs). Согласен, что так себе интрига с КДПВ разрешилась, но… камон ребят, Майкл Джексон...  Ну и как полагается, собственный велосипед DI-модуль — @teqfw/di Это не готовое "к бою" решение, а скорее принципиальная реализация. Все зависимости должны представлять из себя ES-модули и использовать общие для браузера и nodejs возможности. Для разрешения зависимостей в модуле применяется подход awilix: Для запуска back-примера: на сервере: Для запуска front-примера (example.html): нужно выложить модуль на сервер и открыть страницу example.html в браузере (или воспользоваться возможностями IDE). Если открывать example.html напрямую, то в Chrom'е ошибка: Если всё прошло удачно, то в консоли (браузера или nodejs) будет примерно такой вывод: AMD, CommonJS, UMD?  ESM! wiredgeese.com