Нужна ли мне инъекция зависимостей в NodeJS или как справиться с ...?

в настоящее время я создаю некоторые экспериментальные проекты с nodejs. Я запрограммировал много веб-приложений Java EE с Spring и оценил легкость инъекции зависимостей там.

теперь мне любопытно: как сделать инъекцию зависимостей с узлом? Или: мне это вообще нужно? Есть ли концепция замены, потому что стиль программирования отличается?

Я говорю о простых вещах, как обмен объект подключения к базе данных, до сих пор, но я не нашел решение, которое меня удовлетворяет.

21 ответов


короче говоря, вам не нужен контейнер инъекции зависимостей или локатор службы, как в C#/Java. Начиная С Узла.js, использует module pattern, нет необходимости выполнять инъекцию конструктора или свойства. Хотя ты все еще можешь.

самое замечательное в JS заключается в том, что вы можете изменить почти все, чтобы достичь того, что вы хотите. Это пригодится, когда дело доходит до тестирования.

вот мой очень хромой придумал образец.

MyClass.js:

var fs = require('fs');

MyClass.prototype.errorFileExists = function(dir) {
    var dirsOrFiles = fs.readdirSync(dir);
    for (var d in dirsOrFiles) {
        if (d === 'error.txt') return true;
    }
    return false;
};

MyClass.test.js:

describe('MyClass', function(){
    it('should return an error if error.txt is found in the directory', function(done){
        var mc = new MyClass();
        assert(mc.errorFileExists('/tmp/mydir')); //true
    });
});

обратите внимание, как MyClass зависит от fs модуль? Как упоминал @ShatyemShekhar, вы действительно можете сделать конструктор или инъекцию свойств, как и на других языках. Но это не обязательно в Javascript.

в этом случае, вы можете сделать две вещи.

вы можете заглушить fs.readdirSync метод или вы можете вернуть совершенно другой модуль при вызове require.

Способ 1:

var oldmethod = fs.readdirSync;
fs.readdirSync = function(dir) { 
    return ['somefile.txt', 'error.txt', 'anotherfile.txt']; 
};

*** PERFORM TEST ***
*** RESTORE METHOD AFTER TEST ****
fs.readddirSync = oldmethod;

Способ 2:

var oldrequire = require
require = function(module) {
    if (module === 'fs') {
        return {
            readdirSync: function(dir) { 
                return ['somefile.txt', 'error.txt', 'anotherfile.txt']; 
            };
        };
    } else
        return oldrequire(module);

}

ключ должен использовать силу узла.js и Javascript. Обратите внимание, я парень CoffeeScript, поэтому мой синтаксис JS может быть неправильным где-то. Кроме того, я не говорю, что это лучший способ, но это способ. Гуру Javascript, возможно, смогут подключиться к другим решениям.

обновление:

это должно решить ваши конкретный вопрос о соединениях с базой данных. Я бы создал отдельный модуль для вашей инкапсуляции логики подключения к базе данных. Что-то вроде этого:--18-->

MyDbConnection.js: (обязательно выберите лучшее имя)

var db = require('whichever_db_vendor_i_use');

module.exports.fetchConnection() = function() {
    //logic to test connection

    //do I want to connection pool?

    //do I need only one connection throughout the lifecyle of my application?

    return db.createConnection(port, host, databasename); //<--- values typically from a config file    
}

тогда любой модуль, который нуждается в подключении к базе данных, будет просто включать ваш MyDbConnection модуль.

SuperCoolWebApp.js:

var dbCon = require('./lib/mydbconnection'); //wherever the file is stored

//now do something with the connection
var connection = dbCon.fetchConnection(); //mydbconnection.js is responsible for pooling, reusing, whatever your app use case is

//come TEST time of SuperCoolWebApp, you can set the require or return whatever you want, or, like I said, use an actual connection to a TEST database. 

не следуйте этому примеру дословно. Это неудачный пример попытки донести, что ты рычаги module шаблон для управления зависимостями. Надеюсь, это поможет немного больше.


require is на способ управления зависимостями в узел.js и, конечно, он интуитивно понятен и эффективен, но у него также есть свои ограничения.

мой совет-взглянуть на некоторые из контейнеров инъекций зависимостей, доступных сегодня для узла.JS, чтобы иметь представление о том, каковы их плюсы/минусы. Некоторые из них:

просто назвать несколько.

теперь вопрос в том, что вы можете достичь с узлом.JS DI контейнер, по сравнению с простым require?

плюсы:

  • лучшая тестируемость: модули принимают свои зависимости в качестве входных данных
  • инверсия управления: решите, как чтобы подключить модули, не касаясь основного кода приложения.
  • настраиваемый алгоритм разрешения модулей: зависимости имеют" виртуальные " идентификаторы, обычно они не привязаны к пути в файловой системе.
  • Лучшая расширяемость: включена IoC и" виртуальными " идентификаторами.
  • другие модные вещи возможны:
    • асинхронной инициализации
    • управление жизненным циклом модуля
    • расширяемость DI сам контейнер
    • может легко реализовать абстракции более высокого уровня (например, AOP)

плюсы:

  • отличается от узла.js "опыт": не использовать require определенно чувствует, что вы отклоняетесь от образа мышления узла.
  • связь между зависимостью и ее реализацией не всегда является явной. Зависимость может быть решена во время выполнения и под влиянием различных параметры. Код становится более трудным для понимания и отладки
  • более медленное время запуска
  • зрелость (на данный момент): ни одно из текущих решений не действительно популярный на данный момент, так что не так много учебников, нет экосистемы, не тестируется.
  • некоторые контейнеры DI не будут хорошо играть с модульными бундлерами, такими как Browserify и Webpack.

как и все, что связано с разработкой программного обеспечения, выбор между DI или require зависит от ваших требований, сложности системы и стиля программирования.


Я также написал модуль для этого, он называется rewire. Просто используйте npm install rewire и затем:

var rewire = require("rewire"),
    myModule = rewire("./path/to/myModule.js"); // exactly like require()

// Your module will now export a special setter and getter for private variables.
myModule.__set__("myPrivateVar", 123);
myModule.__get__("myPrivateVar"); // = 123


// This allows you to mock almost everything within the module e.g. the fs-module.
// Just pass the variable name as first parameter and your mock as second.
myModule.__set__("fs", {
    readFile: function (path, encoding, cb) {
        cb(null, "Success!");
    }
});
myModule.readSomethingFromFileSystem(function (err, data) {
    console.log(data); // = Success!
});

Я был вдохновлен Натан Макиннес по injectr но использовал другой подход. Я не использую vm чтобы оценить тестовый модуль, на самом деле я использую собственный узел require. Таким образом, ваш модуль ведет себя точно так же, как с помощью require() (кроме модификаций). Также отладка полностью поддерживается.


я знаю, что эта нить довольно старая на данный момент, но я подумал, что я бы присоединился к своим мыслям об этом. TL; DR заключается в том, что из-за нетипизированного, динамического характера JavaScript вы можете на самом деле сделать довольно много, не прибегая к шаблону инъекции зависимостей (DI) или используя структуру DI. Однако, поскольку приложение становится все больше и сложнее, DI определенно может помочь в обслуживании вашего кода.

DI в C#

чтобы понять, почему DI не такой большой нужно в JavaScript, полезно посмотреть на строго типизированный язык, такой как C#. (Извинения тем, кто не знает C#, но это должно быть достаточно легко следовать.) Скажем, у нас есть приложение, которое описывает автомобиль и его рог. Вы бы определили два класса:

class Horn
{
    public void Honk()
    {
        Console.WriteLine("beep!");
    }
}

class Car
{
    private Horn horn;

    public Car()
    {
        this.horn = new Horn();
    }

    public void HonkHorn()
    {
        this.horn.Honk();
    }
}

class Program
{
    static void Main()
    {
        var car = new Car();
        car.HonkHorn();
    }
}

есть несколько проблем с написанием кода таким образом.

  1. на Car класса тесно связан с конкретной реализацией Рога в Horn класса. Если мы хотим изменить тип сигнала используемый автомобилем, мы должны изменить Car класса, хотя его использование рога не меняется. Это также затрудняет тестирование, потому что мы не можем проверить Car класс в изоляции от своей зависимости,Horn класса.
  2. на Car класс отвечает за жизненный цикл Horn класса. В простом примере это не большая проблема,но в реальных приложениях зависимости будут иметь зависимости, которые будут иметь зависимости и т. д. The Car класс должны быть ответственны за создание всего дерева зависимостей. Это не только сложно и однообразно, но и нарушает "единую ответственность" класса. Это должен быть автомобиль, не создавая экземпляров.
  3. невозможно повторно использовать одни и те же экземпляры зависимостей. Опять же, это не важно в этом игрушечном приложении, но рассмотрим подключение к базе данных. Как правило, у вас есть один экземпляр, который является общим для вашего приложение.

теперь, перепишем это использовать шаблон внедрения зависимостей.

interface IHorn
{
    void Honk();
}

class Horn : IHorn
{
    public void Honk()
    {
        Console.WriteLine("beep!");
    }
}

class Car
{
    private IHorn horn;

    public Car(IHorn horn)
    {
        this.horn = horn;
    }

    public void HonkHorn()
    {
        this.horn.Honk();
    }
}

class Program
{
    static void Main()
    {
        var horn = new Horn();
        var car = new Car(horn);
        car.HonkHorn();
    }
}

мы сделали здесь две ключевые вещи. Во-первых, мы ввели интерфейс, который наш Horn класс реализует. Это позволяет нам кодировать Car class к интерфейсу вместо конкретной реализации. Теперь код может взять все, что реализует IHorn. Во-вторых, мы взяли экземпляр рога из Car и передать его в Вместо. Это решает проблемы выше и оставляет его основной функции приложения для управления конкретными экземплярами и их жизненными циклами.

это означает, что можно было бы ввести новый тип рога для автомобиля, чтобы использовать, не касаясь Car класс:

class FrenchHorn : IHorn
{
    public void Honk()
    {
        Console.WriteLine("le beep!");
    }
}

main может просто ввести экземпляр класса. Это также значительно упрощает тестирование. Вы можете создать MockHorn класс для инъекции в Car конструктор чтобы убедиться, что вы тестирование только Car класса в изоляции.

в приведенном выше примере показана инъекция зависимостей вручную. Обычно DI выполняется с помощью фреймворка (например,единство или Ninject в C# мире). Эти фреймворки выполнят всю проводку зависимостей для вас, пройдя график зависимостей и создав экземпляры по мере необходимости.

Стандартный Узел.JS Way

теперь давайте рассмотрим тот же пример в Node.js. Мы, вероятно, сломать наш код в 3 модуля:

// horn.js
module.exports = {
    honk: function () {
        console.log("beep!");
    }
};

// car.js
var horn = require("./horn");
module.exports = {
    honkHorn: function () {
        horn.honk();
    }
};

// index.js
var car = require("./car");
car.honkHorn();

поскольку JavaScript нетипизирован, у нас нет такой же жесткой связи, как и раньше. Нет необходимости в интерфейсах (и они не существуют) как car модуль просто попытается вызвать honk метод на любой horn экспорт модуля.

дополнительно, потому что узел require кэширует все, модули по существу являются синглетонами, хранящимися в контейнере. Любой другой модуль, который выполняет require на the horn модуль получит точно такой же экземпляр. Это делает совместное использование одноэлементных объектов, таких как подключения к базе данных, очень простым.

теперь есть еще вопрос, что car модуль отвечает за получение собственной зависимости horn. Если вы хотите, чтобы автомобиль использовал другой модуль для своего рога, вам придется изменить require заявление в car модуль. Это не очень распространенная вещь, но она вызывает проблемы с тестированием.

в обычный способ, которым люди справляются с проблемой тестирования, - это proxyquire. Из-за динамического характера JavaScript proxyquire перехватывает вызовы require и возвращает любые заглушки/насмешки, которые вы предоставляете вместо этого.

var proxyquire = require('proxyquire');
var hornStub = {
    honk: function () {
        console.log("test beep!");
    }
};

var car = proxyquire('./car', { './horn': hornStub });

// Now make test assertions on car...

этого более чем достаточно для большинства приложений. Если это работает для вашего приложения, тогда идите с ним. Однако, по моему опыту, когда приложения становятся все больше и сложнее, поддерживать такой код становится сложнее.

Ди в В JavaScript

узел.js очень гибкий. Если вы не удовлетворены вышеуказанным методом, вы можете написать свои модули, используя шаблон инъекции зависимостей. В этом шаблоне каждый модуль экспортирует заводскую функцию (или конструктор класса).

// horn.js
module.exports = function () {
    return {
        honk: function () {
            console.log("beep!");
        }
    };
};

// car.js
module.exports = function (horn) {
    return {
        honkHorn: function () {
            horn.honk();
        }
    };
};

// index.js
var horn = require("./horn")();
var car = require("./car")(horn);
car.honkHorn();

это очень похоже на метод C# ранее в том, что index.js модуль отвечает за жизненные циклы и проводку. Модульное тестирование довольно просто, так как вы можете просто передать функции в mocks/stubs. Опять же, если это достаточно хорошо для вашего приложения идут с ним.

болюс DI Framework

в отличие от C#, нет установленных стандартных рамок DI, чтобы помочь с управлением зависимостями. В реестре НПМ существует ряд структур, но ни одна из них не получила широкого распространения. Многие из этих вариантов уже упоминались в других ответах.

я не был особенно доволен любым из доступных вариантов, поэтому я написал свой собственный под названием болюса. Bolus предназначен для работы с кодом, написанным в стиле DI выше, и пытается быть очень сухой и очень простой. Используя то же самое car.js и horn.js модули выше, вы можете переписать index.js модуль с болюсным как:

// index.js
var Injector = require("bolus");
var injector = new Injector();
injector.registerPath("**/*.js");

var car = injector.resolve("car");
car.honkHorn();

основная идея заключается в том, что вы создаете инжектора. Вы регистрируете все ваши модули в инжекторе. Тогда вы просто решаете, что вам нужно. Bolus будет ходить по графику зависимостей и создавать и вводить зависимости по мере необходимости. Вы не экономите много в игрушечном примере, как это, но в больших приложениях со сложными деревьями зависимостей экономия огромна.

Bolus поддерживает кучу отличных функций, таких как дополнительные зависимости и тестовые глобалы, но есть два ключевых преимущества, которые я видел относительно стандартного узла.подход в JS. Во-первых, если у вас есть много подобных приложений, вы можете создать частный модуль npm для своей базы, который создает инжектор и регистрирует полезные объекты на нем. Тогда ваши конкретные приложения могут добавлять, изменять и устранить ее как AngularJS инжектор работает. Во-вторых, вы можете использовать болюс для управления различными контекстами зависимостей. Например, вы можете использовать промежуточное ПО для создания дочернего инжектора по запросу, регистрации идентификатора пользователя, идентификатора сеанса, регистратора и т. д. на инжекторе вместе с любыми модулями в зависимости от этих. Затем решите, что вам нужно для обслуживания запросов. Это дает вам экземпляры ваших модулей на запрос и предотвращает должен пройти лесопогрузчик, etc. вместе с каждым вызовом функции модуля.


Я построил электролит только для этой цели. Другие решения для инъекций зависимостей были слишком инвазивными на мой вкус и возились с глобальным require Это моя особая обида.

электролит охватывает модули, в частности те, которые экспортируют функцию "настройка", как вы видите в Connect/Express middleware. По сути, эти типы модулей являются просто фабриками для некоторого возвращаемого ими объекта.

например, модуль, который создает соединение с базой данных:

var mysql = require('mysql');

exports = module.exports = function(settings) {
  var connection = mysql.createConnection({
    host: settings.dbHost,
    port: settings.dbPort
  });

  connection.connect(function(err) {
    if (err) { throw err; }
  });

  return connection;
}

exports['@singleton'] = true;
exports['@require'] = [ 'settings' ];

то, что вы видите внизу, это аннотации, дополнительный бит метаданных, который электролит использует для создания экземпляров и ввода зависимостей, автоматически связывая компоненты приложения вместе.

для создания подключения к базе данных:

var db = electrolyte.create('database');

электролит транзитивно пересекает @require'D зависимости и вводит экземпляры в качестве аргументов экспортируемой функции.

ключ это малоинвазивная. Этот модуль вполне годен к употреблению, независимый электролита самого. Это означает, что ваши модульные тесты могут тестировать только тестируемый модуль, передавая в макет объекты без необходимости дополнительных зависимостей для перепрограммирования внутренних.

при запуске полного приложения электролит вступает на межмодульном уровне, связывая вещи вместе без необходимости в глобалах, синглетах или чрезмерной сантехнике.


недавно я проверил этот поток по той же причине, что и OP - большинство библиотек, с которыми я столкнулся, временно переписывают оператор require. У меня были смешанные степени успеха с этим методом, и поэтому я в конечном итоге использовал следующий подход.

в контексте экспресс-приложения-я обертываю приложение.js в бутстрапе.файл js:

var path = require('path');
var myapp = require('./app.js');

var loader = require('./server/services/loader.js');

// give the loader the root directory
// and an object mapping module names 
// to paths relative to that root
loader.init(path.normalize(__dirname), require('./server/config/loader.js')); 

myapp.start();

карта объектов, переданная загрузчику, выглядит так:

// live loader config
module.exports = {
    'dataBaseService': '/lib/dataBaseService.js'
}

// test loader config
module.exports = {
    'dataBaseService': '/mocks/dataBaseService.js'
    'otherService' : {other: 'service'} // takes objects too...
};

Затем, а не непосредственно вызывая требовать...

var myDatabaseService = loader.load('dataBaseService');

Если в загрузчике нет псевдонима , то по умолчанию он будет обычным require. Это имеет два преимущества: я могу поменять в любой версии класса, и ее снять нужно использовать относительные имена путей во всем приложении (поэтому, если мне нужен пользовательский lib ниже или выше текущего файла, мне не нужно пересекать, и require будет кэшировать модуль против того же ключа). Это также позволяет мне указать насмешки в любой момент в приложении, а не в непосредственной тестовый набор.

Я только что опубликовал небольшой модуль npm для удобства:

https://npmjs.org/package/nodejs-simple-loader


Я сам этим занимался. Мне не нравится вводить библиотеки Magic dependency utils, которые предоставляют механизмы для захвата импорта моего модуля. Вместо этого я придумал "руководство по дизайну" для моей команды, чтобы довольно явно указать, какие зависимости можно высмеять, введя экспорт заводских функций в моих модулях.

Я широко использую функции ES6 для параметров и деструктурирования, чтобы избежать некоторых шаблонов и предоставить переопределение именованной зависимости механизм.

вот пример:

import foo from './utils/foo';
import bob from './utils/bob';

// We export a factory which accepts our dependencies.
export const factory = (dependencies = {}) => {
  const {
    // The 'bob' dependency.  We default to the standard 'bob' imp if not provided.
    $bob = bob, 
    // Instead of exposing the whole 'foo' api, we only provide a mechanism
    // with which to override the specific part of foo we care about.
    $doSomething = foo.doSomething // defaults to standard imp if none provided.
  } = dependencies;  

  return function bar() {
    return $bob($doSomething());
  }
}

// The default implementation, which would end up using default deps.
export default factory();

и вот пример его использования

import { factory } from './bar';

const underTest = factory({ $bob: () => 'BOB!' }); // only override bob!
const result = underTest();

извините синтаксис ES6 для тех, кто не знаком с ним.


мне всегда нравилась простота концепции МОК - "Вы не должны ничего знать об окружающей среде, вы будете называться кем-то, когда это необходимо"

но все реализации IoC, которые я видел, сделали прямо противоположное - они загромождают код еще большим количеством вещей, чем без него. Итак, я создал свой собственный МОК, который работает так, как я хотел бы - Он остается скрытым и невидимым 90% времени.

Он используется в MonoJS web framework http://monojs.org

Я говорю о простых вещах, таких как совместное использование объекта подключения к базе данных, поэтому далеко, но я не нашел решения, которое удовлетворяло бы меня.

это делается так - зарегистрировать компонент один раз в конфигурации.

app.register 'db', -> 
  require('mongodb').connect config.dbPath

и использовать его в любом месте

app.db.findSomething()

вы можете увидеть полный код определения компонента (с подключением к БД и другими компонентами) здесь https://github.com/sinizinairina/mono/blob/master/mono.coffee

это единственное место, когда вам нужно сказать МОК, что делать, после этого все эти компоненты будут созданы и подключены автоматически, и вам больше не нужно видеть конкретный код МОК в вашем приложении.

сам МОК https://github.com/alexeypetrushin/miconjs


Я думаю, что нам все еще нужна инъекция зависимостей в Nodejs, потому что она ослабляет зависимости между службами и делает приложение более ясным.

вдохновленный Весенние Рамки, Я также реализую свой собственный модуль для поддержки инъекции зависимостей в Nodejs. Мой модуль также может обнаружить code changes и auto reload службы без перезагрузки приложения.

посетите мой проект по адресу:контейнер Buncha - IoC

спасибо ты!


реальность такова, что вы можете проверить свой узел.JS без контейнера IoC, потому что JavaScript-это действительно динамический язык программирования, и вы можете изменить почти все во время выполнения.

рассмотрим следующее:

import UserRepository from "./dal/user_repository";

class UserController {
    constructor() {
        this._repository = new UserRepository();
    }
    getUsers() {
        this._repository.getAll();
    }
}

export default UserController;

так что вы можете переопределить соединение между компонентами во время выполнения. Мне нравится думать, что мы должны отделить наши модули JavaScript.

единственный способ достичь реальной развязки-удалить ссылку на UserRepository:

class UserController {
    constructor(userRepository) {
        this._repository = userRepository;
    }
    getUsers() {
        this._repository.getAll();
    }
}

export default UserController;

это означает, что где-то еще вам нужно будет сделать композицию объекта:

import UserRepository from "./dal/user_repository";
import UserController from "./dal/user_controller";

export default new UserController(new UserRepository());

мне нравится идея делегирования состава объекта контейнеру IoC. Подробнее об этой идее можно узнать в статье текущее состояние инверсии зависимостей в JavaScript. Статья пытается развенчать некоторые "мифы контейнера JavaScript IoC":

Миф 1: нет места для IoC-контейнеров в В JavaScript

МИФ 2: нам не нужны контейнеры МОК, мы уже имеем затяжелители модуля!

Миф 3: инверсия зависимостей = = = инъекционные зависимости

Если Вам также нравится идея использования контейнера IoC, вы можете взглянуть на InversifyJS. Последней версии (2.0.0) поддерживает множество вариантов использования:

  • модули
  • промежуточное ПО ядра
  • использовать классы, строковые литералы или символы в качестве зависимости идентификаторы
  • укол постоянных значений
  • инъекция конструкторов класса
  • инъекции фабрики
  • Автоматический завод
  • инъекция поставщиков (асинхронная фабрика)
  • обработчики активации (используются для ввода прокси)
  • Multi инъекции
  • привязки тегами
  • пользовательские тег декораторы
  • именованные привязки
  • контекстная привязки
  • дружественные исключения (например, циклические зависимости)

вы можете узнать больше об этом на InversifyJS.


для ES6 я разработал этот контейнер https://github.com/zazoomauro/node-dependency-injection

import {ContainerBuilder} from 'node-dependency-injection'

let container = new ContainerBuilder()
container.register('mailer', 'Mailer')

затем вы можете установить, например, выбор транспорта в контейнере:

import {ContainerBuilder} from 'node-dependency-injection'

let container = new ContainerBuilder()
container
  .register('mailer', 'Mailer')
  .addArgument('sendmail')

этот класс теперь намного более гибкий, поскольку вы отделили выбор транспорта от реализации и в контейнер.

теперь, когда почтовая служба находится в контейнере, вы можете ввести ее как зависимость от других классов. Если у вас есть класс NewsletterManager, как это:

class NewsletterManager {
    construct (mailer, fs) {
        this._mailer = mailer
        this._fs = fs
    }
}

export default NewsletterManager

при определении службы newsletter_manager служба почтовой рассылки еще не существует. Используйте ссылочный класс, чтобы сообщить контейнеру о внедрении службы рассылки при инициализации диспетчера рассылки:

import {ContainerBuilder, Reference, PackageReference} from 'node-dependency-injection'
import Mailer from './Mailer'
import NewsletterManager from './NewsletterManager'

let container = new ContainerBuilder()

container
  .register('mailer', Mailer)
  .addArgument('sendmail')

container
  .register('newsletter_manager', NewsletterManager)
  .addArgument(new Reference('mailer'))
  .addArgument(new PackageReference('fs-extra'))

вы также можете настроить контейнер с файлами конфигурации, такими как Yaml, JSON или JS files

контейнер обслуживания можно скомпилировать для различных причин. Эти причины включают проверка любых потенциальных проблем, таких как циклические ссылки и повышение эффективности контейнера.

container.compile()

Это зависит от дизайна вашего приложения. Очевидно, вы можете сделать Java как инъекцию, где вы создаете объект класса с зависимостью, переданной в конструкторе, как это.

function Cache(store) {
   this._store = store;
}

var cache = new Cache(mysqlStore);

Если вы не делаете ООП в JavaScript, вы можете сделать функцию init, которая устанавливает все.

однако есть другой подход, который вы можете принять, который более распространен в системе на основе событий, такой как node.js. Если вы можете моделировать приложение только для (большинство тогда все, что вам нужно сделать, это настроить все(что я обычно делаю, вызывая функцию init) и испускать события из заглушки. Это делает тестирование довольно простым и читаемым.


посмотрите на провалы (простая, но мощная инъекция зависимостей и структура управления сущностью (файлом) для узла.в JS)

https://github.com/devcrust/node-dips


Google di.js работает на nodejs (+ браузер) (+ES6)


недавно я создал библиотеку под названием circuitbox что позволяет использовать инъекцию зависимостей с узлом.js. Он делает истинную инъекцию зависимостей против многих библиотек, основанных на поиске зависимостей, которые я видел. Circuitbox также поддерживает асинхронные процедуры создания и инициализации. Ниже приведен пример:

предположим следующий код в файл под названием consoleMessagePrinter.js

'use strict';

// Our console message printer
// deps is injected by circuitbox with the dependencies
function ConsoleMessagePrinter(deps) {
  return {
    print: function () {
      console.log(deps.messageSource.message());
    }
  };
}

module.exports = ConsoleMessagePrinter;

предположить следующее в файл главный.js

'use strict';

// our simple message source
// deps is injected by circuitbox with the dependencies
var simpleMessageSource = function (deps) {
  return {
    message: function () {
      return deps.message;
    }
  };
};

// require circuitbox
var circuitbox = require('../lib');

// create a circuitbox
circuitbox.create({
  modules: [
    function (registry) {
      // the message to be used
      registry.for('message').use('This is the message');

      // define the message source
      registry.for('messageSource').use(simpleMessageSource)
        .dependsOn('message');

      // define the message printer - does a module.require internally
      registry.for('messagePrinter').requires('./consoleMessagePrinter')
        .dependsOn('messageSource');
    }
  ]
}).done(function (cbx) {

  // get the message printer and print a message
  cbx.get('messagePrinter').done(function (printer) {
    printer.print();
  }, function (err) {
    console.log('Could not recieve a printer');
    return;
  });

}, function (err) {
  console.log('Could not create circuitbox');
});

Circuitbox позволяет определить компоненты и объявить их зависимости как модули. После его инициализации он позволяет получить компонент. Circuitbox автоматически вводит все компоненты, необходимые целевому компоненту, и предоставляет его для использования.

проект находится в альфа-версии. Ваши комментарии, идеи и пожелания приветствуются.

надеюсь, что это помогает!


Я думаю, что другие сообщения проделали большую работу в аргументе для использования DI. Для меня причины

  1. вводить зависимости, не зная их путей. Это означает, что если вы измените расположение модуля на диске или замените его другим, вам не нужно касаться каждого файла, который зависит от него.

  2. это значительно упрощает макет зависимостей для тестирования без боли переопределения глобального require функция таким образом, что работает без проблем.

  3. это поможет вам организовать и рассуждать о вас как о слабо связанных модулях.

но мне было очень трудно найти рамки DI, которые моя команда и я можем легко принять. Так я недавно построил структуру под названием deppie на основе этих функций

  • минимальный API, который можно узнать за несколько минут
  • не требуется дополнительный код / конфигурация / аннотации
  • один к одному прямому отображению в require модули
  • может быть принят частично для работы с существующим кодом

Он должен быть гибким и простым, как это:

var MyClass1 = function () {}
var MyClass2 = function (myService1) {
    // myService1.should.be.instanceof(MyClass1); 
}


container.register('myService1', MyClass1);
container.register('myService2', MyClass2, ['myService1']);

Я написал статью об инъекции зависимостей в node.js.

Я надеюсь, это может помочь вам в этом.


узел.js требует DI столько же, сколько и любая другая платформа. Если вы создаете что-то большое, DI упростит макет зависимостей вашего кода и тщательно протестирует ваш код.

одним из решений было бы передать зависимостей модуля параметры:

module.exports = function (dep1, dep2) {
// private methods

   return {
    // public methods
       test: function(){...}
   }
}

таким образом, зависимости можно легко и естественно высмеивать, и вы можете сосредоточиться на тестировании своего кода без использования какой-либо хитрой сторонней библиотеки.

есть и другие решения (Бродвей, архитектор и т. д.), которые могут помочь вам в этом. хотя они могут сделать больше, чем вы хотите или использовать больше беспорядка.


Я разработал библиотеку, которая обрабатывает инъекцию зависимостей простым способом, что уменьшает код шаблона. Каждый модуль определяется уникальным именем и функцией контроллера. Параметры контроллера отражают зависимости модуля.

подробнее о KlarkJS

пример:

KlarkModule(module, 'myModuleName1', function($nodeModule1, myModuleName2) {
    return {
        log: function() { console.log('Hello from module myModuleName1') }
    };
});
  • myModuleName1 - имя модуля.
  • $nodeModule1 внешняя библиотека от node_module. Имя разрешается в node-module1. Приставка $ указывает, что это внешний модуль.
  • myModuleName2 - это имя внутреннего модуля.
  • возвращаемое значение контроллера используется из других внутренних модулей, когда они определяют параметр myModuleName1.

Я обнаружил этот вопрос в то время как ответ на вопрос на моем собственном модуле DI спрашивая, почему когда-либо понадобится система DI для программирования NodeJS.

ответ явно имел тенденцию к тем, которые даны в этой теме: это зависит. Существуют компромиссы для обоих подходов, и чтение ответов на этот вопрос дает им хорошую форму.

таким образом, реальный ответ на этот вопрос должен заключаться в том, что в некоторых ситуациях вы будете использовать систему DI, в других не.

тем не менее, то, что вы хотите, как разработчик, чтобы не повторять себя и повторно использовать свои услуги в различных приложениях.

это означает, что мы должны написать службы, которые готовы к использованию в системе DI, но не привязаны к библиотекам DI. Для меня это означает, что мы должны написать такие услуги:

module.exports = initDBService;

// Tells any DI lib what it expects to find in it context object
// The $inject prop is the de facto standard for DI imo 
initDBService.$inject = ['ENV'];

// Note the context object, imo, a DI tool should bring
// services in a single context object
function initDBService({ ENV }) {
/// actual service code
}

таким образом, ваш сервис работает независимо от того, используете ли вы его с или без инструмента DI.


Я долгое время работал с .Net, PHP и Java, поэтому я тоже хотел иметь удобную инъекцию зависимостей в NodeJS. Люди сказали, что встроенный DI в NodeJS достаточно, поскольку мы можем получить его с модулем. Но это меня не удовлетворило. Я хотел сохранить модуль не больше, чем класс. Кроме того, я хотел, чтобы DI имел полную поддержку управления жизненным циклом модуля (одноэлементный модуль, переходный модуль и т. д.) но с модулем Node мне приходилось писать ручной код очень часто. Наконец, я хотел сделать Модульный тест проще. Вот почему я создал инъекцию зависимости для себя.

Если вы ищете DI, дайте ему попробовать. Его можно найти здесь:https://github.com/robo-creative/nodejs-robo-container. Это полностью задокументировано. В нем также рассматриваются некоторые общие проблемы с DI и способы их решения в ООП. Надеюсь, это поможет.