В настоящее время я создаю несколько экспериментальных проектов с помощью nodejs. Я программировал множество веб-приложений на Java EE с помощью Spring и оценил простоту внедрения зависимостей.
Теперь мне любопытно: как мне сделать внедрение зависимости с узлом? Или: мне это вообще нужно? Есть ли замена концепции, потому что стиль программирования отличается?
Пока я говорю о простых вещах, таких как совместное использование объекта подключения к базе данных, но я не нашел решения, которое меня устраивало.
Ответы:
Короче говоря, вам не нужен контейнер для внедрения зависимостей или сервисный локатор, как в C # / Java. Так как Node.js использует
module pattern
, нет необходимости выполнять конструктор или внедрение свойства. Хотя вы все еще можете.Самое замечательное в JS состоит в том, что вы можете изменить практически все, чтобы достичь того, чего хотите. Это очень удобно, когда дело доходит до тестирования.
Вот мой очень неубедительный надуманный пример.
MyClass.js
:MyClass.test.js
:Обратите внимание, как
MyClass
зависит отfs
модуля? Как упомянул @ShatyemShekhar, вы действительно можете делать конструктор или внедрение свойств, как в других языках. Но это не обязательно в Javascript.В этом случае вы можете сделать две вещи.
Вы можете заблокировать
fs.readdirSync
метод или вы можете вернуть совершенно другой модуль при вызовеrequire
.Способ 1:
Способ 2:
Ключ заключается в использовании возможностей Node.js и Javascript. Обратите внимание, я парень из CoffeeScript, поэтому мой синтаксис JS может быть где-то неверным. Кроме того, я не говорю, что это лучший способ, но это способ. Гуру Javascript могут быть в состоянии взаимодействовать с другими решениями.
Обновить:
Это должно ответить на ваш конкретный вопрос о подключениях к базе данных. Я бы создал отдельный модуль, чтобы вы могли инкапсулировать логику подключения к вашей базе данных. Что-то вроде этого:
MyDbConnection.js
: (не забудьте выбрать лучшее имя)Затем любой модуль, которому требуется подключение к базе данных, просто включит ваш
MyDbConnection
модуль.SuperCoolWebApp.js
:Не следуйте этому примеру дословно. Это неудачный пример, когда вы пытаетесь сообщить, что вы используете
module
шаблон для управления своими зависимостями. Надеюсь, это поможет немного больше.источник
require('my_logger_library')
, люди, использующие мой компонент, должны будут переопределить требование использовать свою собственную библиотеку. Вместо этого я могу позволить людям передавать обратный вызов, который оборачивает реализацию регистратора в компонентный метод «конструктор» или «метод инициализации». Это цель DI.require
это способ управления зависимостями в Node.js и , конечно , это является интуитивно понятным и эффективным, но он также имеет свои ограничения.Я советую взглянуть на некоторые из доступных сегодня контейнеров Dependency Injection для Node.js, чтобы понять их плюсы и минусы. Некоторые из них:
Просто назвать несколько.
Теперь реальный вопрос: чего вы можете достичь с помощью контейнера Node.js DI по сравнению с простым
require
?Плюсы:
Минусы:
require
определенно означает, что вы отклоняетесь от мышления Node.Как и во всем, что связано с разработкой программного обеспечения, выбор между DI или
require
зависит от ваших требований, сложности вашей системы и вашего стиля программирования.источник
Я знаю, что эта тема довольно старая на данный момент, но я решила, что подумаю об этом. TL; DR заключается в том, что из-за нетипизированной, динамической природы JavaScript вы действительно можете сделать довольно много, не прибегая к шаблону внедрения зависимостей (DI) или используя инфраструктуру DI. Однако по мере того, как приложение становится все больше и сложнее, DI определенно может помочь в поддержке вашего кода.
DI в C #
Чтобы понять, почему DI не так важен в JavaScript, полезно взглянуть на строго типизированный язык, такой как C #. (Приношу извинения тем, кто не знает C #, но за ним должно быть достаточно легко следить.) Скажем, у нас есть приложение, которое описывает автомобиль и его гудок. Вы бы определили два класса:
Есть несколько проблем с написанием кода таким способом.
Car
Класс тесно связан с конкретной реализацией рога вHorn
классе. Если мы хотим изменить тип звукового сигнала, используемого автомобилем, мы должны изменитьCar
класс, даже если его использование не изменится. Это также затрудняет тестирование, потому что мы не можем тестироватьCar
класс изолированно от его зависимости,Horn
класса.Car
Класс отвечает за жизненный цикл этогоHorn
класса. В простом примере, подобном этому, это не является большой проблемой, но в реальных приложениях зависимости будут иметь зависимости, которые будут иметь зависимости и т. Д.Car
Класс должен отвечать за создание всего дерева своих зависимостей. Это не только сложно и повторяется, но и нарушает «единственную ответственность» класса. Следует сосредоточиться на том, чтобы быть автомобилем, а не создавать экземпляры.Теперь давайте сделаем рефакторинг для использования шаблона внедрения зависимостей.
Мы сделали две ключевые вещи здесь. Во-первых, мы представили интерфейс, который
Horn
реализует наш класс. Это позволяет нам кодироватьCar
класс для интерфейса вместо конкретной реализации. Теперь код может принимать все, что реализуетIHorn
. Во-вторых, мы сняли инстанцирование рогаCar
и вместо этого передали его. Это решает описанные выше проблемы и оставляет основной функции приложения управлять конкретными экземплярами и их жизненными циклами.Это означает, что это может привести к появлению нового типа рупора для автомобиля, не касаясь
Car
класса:FrenchHorn
Вместо этого main может просто внедрить экземпляр класса. Это также значительно упрощает тестирование. Вы можете создатьMockHorn
класс для внедрения вCar
конструктор, чтобы убедиться, что вы тестируете толькоCar
класс в изоляции.В приведенном выше примере показано внедрение зависимостей вручную. Обычно DI выполняется с помощью фреймворка (например, Unity или Ninject в мире C #). Эти фреймворки будут выполнять всю проводку зависимостей, обходя график зависимостей и создавая экземпляры по мере необходимости.
Стандартный путь Node.js
Теперь давайте посмотрим на тот же пример в Node.js. Мы, вероятно, разбили бы наш код на 3 модуля:
Поскольку JavaScript нетипизирован, мы не имеем такой же тесной связи, как раньше. Нет необходимости в интерфейсах (и они не существуют), так как
car
модуль просто попытается вызватьhonk
метод для того, чтоhorn
модуль экспортирует.Кроме того, поскольку Node
require
кэширует все, модули по сути являются синглетонами, которые хранятся в контейнере. Любой другой модуль, который выполняетrequire
надhorn
модулем, получит точно такой же экземпляр. Это делает совместное использование одноэлементных объектов, таких как соединения с базой данных, очень простым.Теперь все еще существует проблема, заключающаяся в том, что
car
модуль отвечает за выбор своей собственной зависимостиhorn
. Если вы хотите, чтобы автомобиль использовал другой модуль для своего клаксона, вам нужно изменитьrequire
оператор вcar
модуле. Это не очень распространенная вещь, но она вызывает проблемы с тестированием.Обычно люди справляются с проблемой тестирования с помощью proxyquire . Вследствие динамической природы JavaScript, proxyquire перехватывает вызовы require и возвращает любые заглушки / насмешки, которые вы предоставляете.
Этого более чем достаточно для большинства приложений. Если это работает для вашего приложения, то иди с ним. Тем не менее, по моему опыту, поскольку приложения становятся все больше и сложнее, поддерживать такой код становится все труднее.
DI в JavaScript
Node.js очень гибкий. Если вы не удовлетворены описанным выше методом, вы можете написать свои модули, используя шаблон внедрения зависимостей. В этом шаблоне каждый модуль экспортирует фабричную функцию (или конструктор класса).
Это очень похоже на метод C # ранее в том, что
index.js
модуль отвечает за жизненные циклы экземпляров и проводку. Модульное тестирование довольно простое, так как вы можете просто передать макеты / заглушки функциям. Опять же, если это достаточно хорошо для вашего приложения, используйте его.Bolus DI Framework
В отличие от C #, не существует установленных стандартных структур DI, которые бы помогли вам в управлении зависимостями. В реестре npm есть несколько платформ, но ни одна из них не получила широкого распространения. Многие из этих вариантов уже упоминались в других ответах.
Я не был особенно доволен ни одним из доступных вариантов, поэтому я написал свой болюс . Bolus предназначен для работы с кодом, написанным в стиле DI выше, и старается быть очень СУХИМЫМ и очень простым. Используя тот же самый
car.js
иhorn.js
модулей выше, вы можете переписатьindex.js
модуль с болюса , как:Основная идея заключается в том, что вы создаете инжектор. Вы регистрируете все свои модули в инжекторе. Тогда вы просто решаете, что вам нужно. Bolus будет обходить граф зависимостей и создавать и вводить зависимости по мере необходимости. В таком игрушечном примере вы не сильно экономите, но в больших приложениях со сложными деревьями зависимостей экономия огромна.
Bolus поддерживает множество полезных функций, таких как необязательные зависимости и тестовые глобалы, но я вижу два ключевых преимущества по сравнению со стандартным подходом Node.js. Во-первых, если у вас много похожих приложений, вы можете создать частный модуль npm для своей базы, который создает инжектор и регистрирует на нем полезные объекты. Тогда ваши конкретные приложения могут добавлять, переопределять и разрешать по мере необходимости так же, как AngularJSинжектор работает. Во-вторых, вы можете использовать болюс для управления различными контекстами зависимостей. Например, вы можете использовать промежуточное ПО для создания дочернего инжектора для каждого запроса, регистрации идентификатора пользователя, идентификатора сеанса, регистратора и т. Д. В инжекторе вместе с любыми модулями, в зависимости от них. Затем решите, что вам нужно для обслуживания запросов. Это дает вам экземпляры ваших модулей для каждого запроса и предотвращает необходимость передавать регистратор и т. Д. При каждом вызове функции модуля.
источник
proxyquire
например,sinon
которая позволяет вам делать очень лаконичные макеты, например,let readFileStub = sinon.stub(fs, 'readFile').yields(new Error('something went wrong'));
и затем последующие вызовыfs.readFile
будут возвращать ошибку, пока вы не вернете заглушку черезreadFileStub.restore()
. Лично я нахожу DI сомнительным использованием, потому что я чувствую, что это почти требует использования классов / объектов, что является сомнительным предположением, учитывая функциональные предпочтения javascript.jest
,rewire
,proxyquire
и т.д.? Спасибо.Я также написал модуль для этого, он называется rewire . Просто используйте,
npm install rewire
а затем:Меня вдохновил инъектор Натана Макиннеса, но я использовал другой подход. Я не использую
vm
для оценки тестового модуля, на самом деле я использую собственное требование узла. Таким образом , ваш модуль ведет себя так же , как с помощьюrequire()
( за исключением ваших изменений). Также полностью поддерживается отладка.источник
Я построил Электролит только для этой цели. Другие решения для инъекций зависимостей были слишком агрессивными для моих вкусов, и возиться с глобальными
require
проблемами - моя особая претензия.Электролит включает в себя модули, особенно те, которые экспортируют функцию «настройки», как вы видите в промежуточном программном обеспечении Connect / Express. По сути, эти типы модулей являются просто фабриками для некоторого объекта, который они возвращают.
Например, модуль, который создает соединение с базой данных:
Внизу вы видите аннотации , дополнительный бит метаданных, которые Electrolyte использует для создания и внедрения зависимостей, автоматически соединяя компоненты вашего приложения.
Чтобы создать соединение с базой данных:
Электролит транзитивно пересекает
@require
зависимости d и вводит экземпляры в качестве аргументов экспортируемой функции.Ключ в том, что это минимально инвазивно. Этот модуль полностью пригоден для использования независимо от самого электролита. Это означает, что ваши модульные тесты могут тестировать только тестируемый модуль , передавая фиктивные объекты без необходимости в дополнительных зависимостях для перепрограммирования внутренних компонентов.
При запуске полного приложения, Electrolyte входит на межмодульном уровне, соединяя все вместе без необходимости глобализаций, синглетонов или чрезмерной сантехники.
источник
connect()
бросков? Хотя я не знаком с MySql API для Node, я ожидаю, что этот вызов будет асинхронным, поэтому иллюстрация не совсем понятна.ioc.create
с юнит-теста. Модульное тестирование должно проверять только тестируемый модуль и не содержать других зависимостей, включая электролит. Следуя этому совету, вы бы поступилиobjToTest = require('modulename')(mockObj1, mockObj2);
Я посмотрел на это сам. Мне не нравится вводить волшебные зависимости, использует библиотеки, которые предоставляют механизмы для захвата импорта моего модуля. Вместо этого я придумал «руководство по проектированию» для своей команды, чтобы довольно явно указать, какие зависимости можно смоделировать, введя экспорт фабричных функций в мои модули.
Я широко использую функции ES6 для параметров и деструктуризации, чтобы избежать некоторых шаблонов и обеспечить именованный механизм переопределения зависимостей.
Вот пример:
И вот пример его использования
Извините синтаксис ES6 для тех, кто не знаком с ним.
источник
Я недавно проверил этот поток по той же причине, что и OP - большинство библиотек, с которыми я столкнулся, временно переписывают оператор require. Я имел смешанные степени успеха с этим методом, и поэтому я использовал следующий подход.
В контексте экспресс-приложения - я оборачиваю app.js в файл bootstrap.js:
Карта объектов, переданная загрузчику, выглядит следующим образом:
Тогда вместо прямого звонка требуется ...
Если в загрузчике нет псевдонима, то по умолчанию он будет обычным требованием. Это имеет два преимущества: я могу поменяться любой версией класса, и это избавит от необходимости использовать относительные имена путей во всем приложении (поэтому, если мне нужна пользовательская библиотека ниже или выше текущего файла, мне не нужно проходить , и require будет кешировать модуль против того же ключа). Это также позволяет мне указывать макеты в любой точке приложения, а не в наборе немедленных тестов.
Я только что опубликовал небольшой модуль npm для удобства:
https://npmjs.org/package/nodejs-simple-loader
источник
Реальность такова, что вы можете протестировать ваш node.js без контейнера IoC, потому что JavaScript - это действительно динамический язык программирования, и вы можете изменять практически все во время выполнения.
Учтите следующее:
Таким образом, вы можете переопределить связь между компонентами во время выполнения. Мне нравится думать, что мы должны стремиться отделить наши модули JavaScript.
Единственный способ добиться реального разделения - удалить ссылку на
UserRepository
:Это означает, что где-то еще вам нужно будет сделать композицию объекта:
Мне нравится идея делегирования композиции объекта в контейнер IoC. Вы можете узнать больше об этой идее в статье Текущее состояние инверсии зависимостей в JavaScript . Статья пытается разоблачить некоторые «мифы о контейнере JavaScript IoC»:
Если вам также нравится идея использования контейнера IoC, вы можете взглянуть на InversifyJS. Последний выпуск (2.0.0) поддерживает множество вариантов использования:
Вы можете узнать больше об этом в InversifyJS .
источник
Для ES6 я разработал этот контейнер https://github.com/zazoomauro/node-dependency-injection
Тогда вы можете установить, например, выбор транспорта в контейнере:
Этот класс теперь гораздо более гибкий, поскольку вы разделили выбор транспорта из реализации и в контейнер.
Теперь, когда почтовый сервис находится в контейнере, вы можете внедрить его как зависимость от других классов. Если у вас есть класс NewsletterManager, подобный этому:
При определении службы newsletter_manager служба почтовой рассылки еще не существует. Используйте класс Reference, чтобы указать контейнеру внедрить службу почтовой программы при инициализации менеджера новостной рассылки:
Вы также можете настроить контейнер с помощью файлов конфигурации, таких как файлы Yaml, Json или JS
Служебный контейнер может быть скомпилирован по разным причинам. Эти причины включают проверку любых потенциальных проблем, таких как циклические ссылки и повышение эффективности контейнера.
источник
Это зависит от дизайна вашего приложения. Очевидно, что вы можете сделать Java-инъекцию, где вы создаете объект класса с зависимостью, передаваемой в конструктор, как это.
Если вы не делаете ООП в javascript, вы можете сделать функцию инициализации, которая все настраивает.
Тем не менее, есть другой подход, который вы можете использовать, который более распространен в системе, основанной на событиях, такой как node.js. Если вы можете смоделировать ваше приложение только для того, чтобы (в большинстве случаев) воздействовать на события, тогда все, что вам нужно сделать, это настроить все (что я обычно делаю, вызывая функцию init) и генерировать события из заглушки. Это делает тестирование довольно простым и читабельным.
источник
Мне всегда нравилась простота концепции IoC: «Вам не нужно ничего знать об окружающей среде, вам будет кто-то позвонить, когда потребуется»
Но все реализации IoC, которые я видел, делали с точностью до наоборот - они загромождали код даже большим количеством вещей, чем без него. Итак, я создал свой собственный IoC, который работает так, как мне бы хотелось - он остается скрытым и невидимым 90% времени .
Он используется в веб-фреймворке MonoJS http://monojs.org
Это сделано так - зарегистрируйте компонент один раз в конфиге.
И использовать его где угодно
Вы можете увидеть полный код определения компонента (с подключением к БД и другими компонентами) здесь https://github.com/sinizinairina/mono/blob/master/mono.coffee.
Это единственное место, когда вы должны указать IoC, что делать, после этого все эти компоненты будут созданы и подключены автоматически, и вам больше не нужно будет видеть специфичный для IoC код в вашем приложении.
Сам IoC https://github.com/alexeypetrushin/miconjs
источник
Я думаю, что нам все еще нужно внедрение зависимостей в Nodejs, потому что оно ослабляет зависимости между сервисами и делает приложение более понятным.
Вдохновленный Spring Framework , я также реализую свой собственный модуль для поддержки внедрения зависимостей в Nodejs. Мой модуль также может обнаруживать
code changes
иauto reload
обслуживать без перезагрузки приложения.Посетите мой проект по адресу: Бунча - контейнер IoC
Спасибо!
источник
Взгляните на провалы (простая, но мощная инфраструктура внедрения зависимостей и сущностей (файлов) для Node.js)
https://github.com/devcrust/node-dips
источник
Я работал с .Net, PHP и Java в течение долгого времени, поэтому я хотел иметь удобное внедрение зависимостей и в NodeJS. Люди сказали, что встроенного DI в NodeJS достаточно, поскольку мы можем получить его с помощью Module. Но это не удовлетворило меня хорошо. Я хотел сохранить модуль не более, чем класс. Кроме того, я хотел, чтобы DI имел полную поддержку управления жизненным циклом модулей (одноэлементный модуль, переходный модуль и т. Д.), Но с модулем Node мне приходилось очень часто писать ручной код. Наконец, я хотел облегчить юнит тест. Вот почему я создал инъекцию зависимости для себя.
Если вы ищете DI, попробуйте. Его можно найти здесь: https://github.com/robo-creative/nodejs-robo-container . Это полностью задокументировано. Также рассматриваются некоторые распространенные проблемы с DI и способы их решения ООП. Надеюсь, поможет.
источник
Недавно я создал библиотеку с именем circuitbox, которая позволяет вам использовать внедрение зависимостей с помощью node.js. Он действительно внедряет зависимости по сравнению со многими библиотеками на основе поиска зависимостей, которые я видел. Circuitbox также поддерживает процедуры асинхронного создания и инициализации. Ниже приведен пример:
Предположим, что следующий код находится в файле с именем consoleMessagePrinter.js
Предположим, что следующее находится в файле main.js
Circuitbox позволяет вам определять ваши компоненты и объявлять их зависимости как модули. После инициализации он позволяет вам получить компонент. Circuitbox автоматически внедряет все компоненты, необходимые целевому компоненту, и предоставляет его вам для использования.
Проект в альфа-версии. Ваши комментарии, идеи и отзывы приветствуются.
Надеюсь, поможет!
источник
Я думаю, что другие посты проделали большую работу в аргументе для использования DI. Для меня причины
Введите зависимости, не зная их пути. Это означает, что если вы меняете расположение модуля на диске или меняете его другим, вам не нужно трогать каждый файл, который от него зависит.
Это позволяет намного проще смоделировать зависимости для тестирования без необходимости переопределения глобальной
require
функции таким образом, чтобы она работала без проблем.Это поможет вам организовать и обосновать ваше приложение как слабо связанные модули.
Но мне было очень трудно найти структуру DI, которую я и моя команда легко могли бы принять. Поэтому я недавно построил фреймворк под названием Деппи на основе этих функций
require
модулямиисточник
Это должно быть гибким и простым, как это:
Я написал статью о внедрении зависимостей в node.js.
Я надеюсь, что это может помочь вам в этом.
источник
Node.js требует DI столько же, сколько любая другая платформа. Если вы создаете что-то большое, DI облегчит моделирование зависимостей вашего кода и тщательное тестирование кода.
Например, ваши модули уровня базы данных не должны просто потребоваться в ваших модулях бизнес-кода, потому что при модульном тестировании этих модулей бизнес-кода происходит загрузка и подключение к базе данных.
Одним из решений было бы передать зависимости как параметры модуля:
Таким образом, зависимости могут быть легко и естественно смоделированы, и вы можете сосредоточиться на тестировании своего кода без использования какой-либо хитрой сторонней библиотеки.
Существуют и другие решения (бродвей, архитектор и т. Д.), Которые могут помочь вам в этом. хотя они могут делать больше, чем вы хотите, или использовать больше беспорядка.
источник
Я разработал библиотеку, которая обрабатывает внедрение зависимостей простым способом, который уменьшает стандартный код. Каждый модуль определяется уникальным именем и функцией контроллера. Параметры контроллера отражают зависимости модуля.
Узнайте больше на KlarkJS
Краткий пример:
myModuleName1
это имя модуля.$nodeModule1
это внешняя библиотека изnode_module
. Имя разрешается доnode-module1
. Префикс$
указывает, что это внешний модуль.myModuleName2
это имя внутреннего модуля.myModuleName1
.источник
Я обнаружил этот вопрос, отвечая на вопрос в моем собственном модуле DI, спрашивающий, зачем когда-либо понадобится система DI для программирования NodeJS.
Ответ был явно склонен к тем, которые приведены в этой теме: это зависит. Для обоих подходов есть компромиссы, и чтение ответов на этот вопрос дает им хорошую форму.
Таким образом, реальный ответ на этот вопрос должен состоять в том, что в некоторых ситуациях вы будете использовать систему DI, в других - нет.
Тем не менее, вы, как разработчик, не хотите повторяться и повторно использовать свои услуги в различных приложениях.
Это означает, что мы должны написать сервисы, которые готовы к использованию в системе DI, но не привязаны к библиотекам DI. Для меня это означает, что мы должны написать такие сервисы:
Таким образом, ваш сервис работает независимо от того, используете ли вы его с инструментом DI или без него.
источник
TypeDI является самым сладким из всех упомянутых здесь, посмотрите этот код в TypeDI
Посмотрите и этот код:
источник