[JavaScript] @teqfw/di
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Некоторые любят ездить велосипедах, а некоторые любят их изобретать. Я отношусь к тем, кто изобретает велосипеды, чтобы на них ездить. Пару лет назад я уже писал на Хабр про этот свой "велосипед" - контейнер внедрения зависимостей (DI container) для JavaScript. Последующее обсуждение принципов работы DI-контейнеров и их отличие от "Локатора Сервисов" достаточно сильно продвинуло меня в понимании работы моего собственного "велосипеда" и вылилось не только в ряд статей на Хабре (раз, два, три, четыре), но и в значительной доработке самого "велосипеда".Под катом - описание работы DI-контейнера (@teqfw/di) по состоянию на текущий момент. Ограничения: контейнер написан на чистом JavaScript (ES2015+), работает только с ES2015+ кодом, оформленным в ES-модули с расширением *.mjs . Преимущества: позволяет загружать и использовать одни и те же модули как в браузере, так и в nodejs-приложениях без дополнительной транспиляции.Основы работы DI-контейнеровТиповое обращение к абстрактному контейнеру объектов выглядит примерно так:
const obj = container.get(id);
Последовательность действий контейнера:
- Определить по id , что за объект хочет получить вызывающая сторона.
- Проверить контейнер на предмет наличия в нём запрашиваемого объекта, если объект есть в контейнере — вернуть его.
- Если же объект нужно создавать, то по id определить, где находится файл с исходным кодом объекта и загрузить исходники.
- Разобрать спецификацию входных зависимостей конструктора объекта (набор идентификаторов).
- Найти в контейнере зависимости согласно спецификации или создать заново.
- Создать запрашиваемый объект с использованием собранных зависимостей.
- Сохранить созданный объект в контейнере для последующего использования (при необходимости).
- Вернуть созданный объект вызывающей стороне.
Последовательность действий рекурсивная — повторяется в пункте 5 для каждой зависимости, до тех пор, пока всё дерево зависимостей запрашиваемого объекта не будет создано. ES-модульПрежде всего нужно понимать, что может экспортировать ES-модуль — т.е., что именно может использовать DI-контейнер для создания объектов после загрузки исходного кода ES-модуля.
const obj = {name: 'Simple Object'};
class Clazz {
constructor(spec) {
this.name = 'instance from class constructor';
}
}
function Factory(spec) {
return {name: 'instance from factory'};
}
export {
obj as ObjTmpl,
Clazz as default,
Factory,
}
А это пример того, как вызывающая сторона могла бы создавать объекты при помощи кода из этого ES-модуля (вручную, без использования контейнера):
import Def from './es6.mjs';
import {Factory} from './es6.mjs';
import {ObjTmpl} from './es6.mjs';
const spec = {}; // empty specification
const instClass = new Def(spec);
const instFact = Factory(spec);
const instTmpl = Object.assign(ObjTmpl, {});
Итого, DI-контейнер, после загрузки ES-модуля, может создавать новые объекты на основе экспорта модуля:
- используя классы;
- используя фабричные функции;
Идентификаторы зависимостейИдентификатор зависимости — это строка, идентифицирующая объект, который ожидает получить конструктор объекта (фабричная функция) в качестве зависимости, или который DI-контейнер должен вернуть вызывающей стороне:
constructor(spec) {
const dep = spec['depId'];
}
...
await container.get('dep1');
Именованные и импортируемыеВ самом простом случае разработчик может создавать объекты вручную и помещать их прямо в контейнер под произвольными идентификаторами:
import Container from '@teqfw/di';
const container = new Container();
container.set('dep1', {name: 'first'});
container.set('dep2', {name: 'second'});
const obj = await container.get('dep1');
Но в большинстве случаев нас интересует способ автоматического нахождения контейнером ES-модуля, подгрузка исходников и определение способа создания зависимости (класс, фабричная функция или шаблон-объект).В @teqfw/di все идентификаторы делятся на две большие группы:
- именованные зависимости: название начинается со строчной буквы (connection, conf, i18n, …);
- импортируемые зависимости: названия начинаются с прописной буквы (EsModuleId);
Именованные зависимости добавляются в контейнер вручную через container.set(id, obj), для импортируемых зависимостей есть правила сопоставления идентификаторов путям к исходникам (рассмотрим позднее).Модуль и экспорт модуляВ @teqfw/di для загрузки ES-модуля используется динамический импорт, результатом которого является специальный объект Module (см. пункт “Модули” в “Javascript: исходный код и его отображение при отладке”).Необходимо различать, хотим ли мы использовать в качестве зависимости модуль целиком или какой-то определённый экспорт из данного модуля. В @teqfw/di для этого используется знак #:
- EsModuleId: идентификатор для модуля целиком;
- EsModuleId#ExportName: идентификатор для экспорта с именем ExportName в модуле EsModuleId;
- EsModuleId#default и EsModuleId#: оба идентификатора равнозначны и указывают на экспорт default в модуле EsModuleId;
Функция и результат функцииЗависимости объекта в @teqfw/di передаются в спецификации в конструктор или фабричную функцию:
class Clazz {
constructor(spec) {}
}
function Factory(spec) {}
Как различить случай, когда мы хотим получить от контейнера сам класс (функцию), а когда — экземпляр объекта данного класса (результат работы функции)? В идентификаторе зависимости для @teqfw/di это отражается при помощи символа $:
- EsModuleId#ExportName: получить объект (класс, функцию) с именем ExportName из модуля EsModuleId.
- EsModuleId#ExportName$: получить объект, созданный при помощи конструктора (фабричной функции), являющегося экспортом ExportName модуля EsModuleId.
Для создания объекта из default-экспорта ES-модуля нижеприведенные идентификаторы равнозначны:
- EsModuleId#default$
- EsModuleId#$
- EsModuleId$
Singleton и новый экземпляр Иногда контейнер должен использовать один и тот же экземпляр в качестве зависимости для всех объектов приложения (или некоторых), а иногда каждый раз должен создаваться новый экземпляр объекта. В идентификаторах зависимости это отражается через удвоение символа "доллар" -$$:
- EsModuleId$ и EsModuleId#ExportName$: объект создается один раз (при первом запросе) и сохраняется в контейнере, для всех последующих запросов используется ранее сохраненный объект.
- EsModuleId$$ и EsModuleId#ExportName$$: каждый раз создается новый экземпляр объекта.
При этом неважно, какой из объектов первым запросил создание singleton’а — все остальные получат этот же экземпляр. В некотором смысле любой DI-контейнер является global-объектом. Кто-то может сказать, что это антипаттерн, и отказаться от использования DI — его полное право.Сводная таблица идентификаторов Итого в @teqfw/di используются следующие идентификаторы зависимостей:
let id1 = 'named'; // named singleton been added manually
let id2 = 'EsModId'; // ES module
let id3 = 'EsModId#'; // default export of ES module
let id4 = 'EsModId#name'; // named export of ES module
let id5 = 'EsModId$'; // singleton from default export
let id6 = 'EsModId$$'; // new instance from default export
let id7 = 'EsModId#name$'; // singleton from named export
let id8 = 'EsModId#name$$'; // new instance from named export
Декларация зависимостейВся мощь контейнера раскрывается тогда, когда мы описываем зависимости, необходимые для создания объекта, в конструкторе (фабричной функции). В @teqfw/di это делается так:
constructor(spec) {
const named = spec['namedSingleton'];
const inst = spec['EsModId#name$$'];
const single = spec['EsModId$'];
}
Особенностью @teqfw/di является то, что контейнер прерывает процесс создания запрошенного объекта, если обнаруживает неизвестную зависимость, подгружает исходники зависимости и создает зависимость, после чего вновь пытается создать запрошенный объект. Таким образом, первые строки конструктора запрошенного объекта могут выполняться несколько раз, если в процессе приходилось несколько раз прерывать процесс и подгружать нужные исходники.Загрузка исходников Чтобы контейнер по идентификатору зависимости мог обнаружить файл с исходным кодом соответствующего ES-модуля, нужна карта сопоставления идентификаторов зависимостей файлам с исходниками. В @teqfw/di добавлением позиций в карту делается так:
container.addSourceMapping('EsModId', './relative/path');
container.addSourceMapping('EsModId', '/absolute/path', true);
Первый способ (с относительной адресацией) в основном применяется, если контейнер используется в браузере, второй — в nodejs-приложениях. Тем не менее, оба способа могут применяться в обеих средах.В карте сопоставления, используемой контейнером, прописывается корневой каталог с исходниками, дальнейшее сопоставление идентификаторов исходникам идет через использование namespace’ов, где разделителем имен каталогов является _:
EsModId_PathTo_Mod => /absolute/path/PathTo/Mod.mjs
РезюмеDI-контейнер @teqfw/di позволяет использовать в качестве зависимостей как сами ES-модули, так и отдельные элементы из экспорта ES-модулей, а также создавать новые экземпляры объектов или использовать один единственный объект для всего приложения. Причем один и тот же код может использоваться как в браузерах, так и в nodejs-приложениях.Типовой код для ES-модуля, используемого в @teqfw/di:
export default class Mod {
constructor(spec) {
const Clazz = spec['Lib_Dep#'];
const single = spec['Lib_Dep$'];
const inst = spec['Lib_Dep$$'];
// ...
}
}
Обычный import также можно использовать в коде, но в таком случае код теряет возможность быть использованным одновременно и в браузерах, и в nodejs-приложениях, т.к. браузерный формат import'а не совместим с nodejs-форматом.
===========
Источник:
habr.com
===========
Похожие новости:
- Состоялся релиз браузера Vivaldi 4.0 для десктопа и Android
- [Информационная безопасность, Криптография, JavaScript, Node.JS, Криптовалюты] Поиск коллизий в SHA-256 на платформе Node.js при помощи Bitcoin Hasher
- [Системное администрирование, Серверное администрирование, DevOps] Аварии как опыт #3. Как мы спасали свой мониторинг во время аварии в OVH
- [Информационная безопасность] Relay атаки
- [JavaScript, ReactJS] react-router: Три метода рендеринга маршрутов (компонентный, рендеринговый и дочерний) (перевод)
- [Программирование, Программирование микроконтроллеров, Разработка под Arduino, DIY или Сделай сам, Электроника для начинающих] «Перетягивание диода» или устраиваем соревнование между CANNY 3 TINY PRO и Arduino
- [Open source, Работа с видео, IPTV, Видеотехника] Kodi 20 получит название «Nexus»
- [JavaScript, Веб-аналитика, Интернет-маркетинг, Повышение конверсии, Поисковая оптимизация] Как настроить Facebook Conversion API с помощью GTM Server Side
- [Работа с видео, JavaScript, Программирование, Видеоконференцсвязь] Streaming multiple RTSP IP cameras on YouTube and/or Facebook
- [JavaScript, Программирование, Карьера в IT-индустрии] Публичное техническое собеседование на мидл фронтенд-разработчика: 15 июня в 19.00
Теги для поиска: #_javascript, #_teqfw, #_di, #_dependency_injection, #_javascript
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:52
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Некоторые любят ездить велосипедах, а некоторые любят их изобретать. Я отношусь к тем, кто изобретает велосипеды, чтобы на них ездить. Пару лет назад я уже писал на Хабр про этот свой "велосипед" - контейнер внедрения зависимостей (DI container) для JavaScript. Последующее обсуждение принципов работы DI-контейнеров и их отличие от "Локатора Сервисов" достаточно сильно продвинуло меня в понимании работы моего собственного "велосипеда" и вылилось не только в ряд статей на Хабре (раз, два, три, четыре), но и в значительной доработке самого "велосипеда".Под катом - описание работы DI-контейнера (@teqfw/di) по состоянию на текущий момент. Ограничения: контейнер написан на чистом JavaScript (ES2015+), работает только с ES2015+ кодом, оформленным в ES-модули с расширением *.mjs . Преимущества: позволяет загружать и использовать одни и те же модули как в браузере, так и в nodejs-приложениях без дополнительной транспиляции.Основы работы DI-контейнеровТиповое обращение к абстрактному контейнеру объектов выглядит примерно так: const obj = container.get(id);
const obj = {name: 'Simple Object'};
class Clazz { constructor(spec) { this.name = 'instance from class constructor'; } } function Factory(spec) { return {name: 'instance from factory'}; } export { obj as ObjTmpl, Clazz as default, Factory, } import Def from './es6.mjs';
import {Factory} from './es6.mjs'; import {ObjTmpl} from './es6.mjs'; const spec = {}; // empty specification const instClass = new Def(spec); const instFact = Factory(spec); const instTmpl = Object.assign(ObjTmpl, {});
constructor(spec) {
const dep = spec['depId']; } ... await container.get('dep1'); import Container from '@teqfw/di';
const container = new Container(); container.set('dep1', {name: 'first'}); container.set('dep2', {name: 'second'}); const obj = await container.get('dep1');
class Clazz {
constructor(spec) {} } function Factory(spec) {}
let id1 = 'named'; // named singleton been added manually
let id2 = 'EsModId'; // ES module let id3 = 'EsModId#'; // default export of ES module let id4 = 'EsModId#name'; // named export of ES module let id5 = 'EsModId$'; // singleton from default export let id6 = 'EsModId$$'; // new instance from default export let id7 = 'EsModId#name$'; // singleton from named export let id8 = 'EsModId#name$$'; // new instance from named export constructor(spec) {
const named = spec['namedSingleton']; const inst = spec['EsModId#name$$']; const single = spec['EsModId$']; } container.addSourceMapping('EsModId', './relative/path');
container.addSourceMapping('EsModId', '/absolute/path', true); EsModId_PathTo_Mod => /absolute/path/PathTo/Mod.mjs
export default class Mod {
constructor(spec) { const Clazz = spec['Lib_Dep#']; const single = spec['Lib_Dep$']; const inst = spec['Lib_Dep$$']; // ... } } =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:52
Часовой пояс: UTC + 5