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

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

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

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

Ответ 1

Короче говоря, вам не нужен контейнер инъекций зависимостей или локатор обслуживания, как на С#/Java. Поскольку Node.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);

}

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

Update:

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

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 для управления вашими зависимостями. Надеюсь, это поможет немного больше.

Ответ 2

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

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

Просто, чтобы назвать несколько.

Теперь реальный вопрос: что вы можете достичь с помощью контейнера Node.js DI, по сравнению с простым require?

Плюсы:

  • лучшая тестируемость: модули принимают свои зависимости в качестве входных данных
  • Inversion of Control: выберите способ подключения ваших модулей, не касаясь основного кода вашего приложения.
  • настраиваемый алгоритм для решения модулей: зависимости имеют "виртуальные" идентификаторы, обычно они не привязаны к пути в файловой системе.
  • Улучшенная расширяемость: включена IoC и "виртуальными" идентификаторами.
  • Возможны другие причудливые вещи:
    • Асинхронная инициализация
    • Управление жизненным циклом модуля
    • Расширяемость самого контейнера DI
    • Может легко реализовать абстракции более высокого уровня (например, АОП)

Минусы:

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

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

Ответ 3

Я также написал модуль для этого, он называется 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!
});

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

Ответ 4

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

DI в С#

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

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();
    }
}

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

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

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

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 для интерфейса вместо конкретной реализации. Теперь код может принимать все, что реализует IHorn. Во-вторых, мы взяли экземпляр рога из Car и передаем его вместо. Это устраняет перечисленные выше проблемы и оставляет его основной функции приложения для управления конкретными экземплярами и их жизненными циклами.

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

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

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

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

Стандарт Node.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.

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

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

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

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

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

// Now make test assertions on car...

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

DI в JavaScript

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

// 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();

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

Рамочная платформа Bolus DI

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

Я не был особенно доволен любым из доступных вариантов, поэтому я написал свой собственный bolus. Bolus предназначен для работы с кодом, написанным в стиле DI выше, и пытается быть очень DRY и очень прост. Используя те же самые 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 поддерживает множество полезных функций, таких как дополнительные зависимости и тестовые глобальные переменные, но есть два ключевых преимущества, которые я видел относительно стандартного подхода Node.js. Во-первых, если у вас много подобных приложений, вы можете создать частный модуль npm для своей базы, который создает инжектор и регистрирует на нем полезные объекты. Тогда ваши конкретные приложения могут добавлять, переопределять и разрешать по мере необходимости, как работает AngularJS. Во-вторых, вы можете использовать болюс для управления различными контекстами зависимостей. Например, вы можете использовать промежуточное программное обеспечение для создания дочернего инжектора для запроса, зарегистрировать идентификатор пользователя, идентификатор сеанса, регистратор и т.д. На инжекторе вместе с любыми модулями в зависимости от них. Затем разрешите то, что вам нужно для обслуживания запросов. Это дает вам экземпляры ваших модулей для каждого запроса и не позволяет передавать логгер и т.д. Каждому вызову функции модуля.

Ответ 5

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

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

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

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' ];

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

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

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

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

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

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

Ответ 6

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

В контексте экспресс-приложения - я переношу app.js в файл bootstrap.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');

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

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

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

Ответ 7

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

Я широко использую функции 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 для тех, кто не знаком с ним.

Ответ 8

Мне всегда нравилась простота концепции IoC - "Вам не нужно ничего знать о среде, вы будете вызваны кем-то при необходимости"

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

Он используется в веб-среде MonoJS http://monojs.org

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

Выполняется так: зарегистрируйте компонент один раз в config.

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

И используйте его в любом месте

app.db.findSomething()

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

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

Сам IoC https://github.com/alexeypetrushin/miconjs

Ответ 9

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

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

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

Спасибо!

Ответ 10

Реальность заключается в том, что вы можете протестировать ваш node.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: нам не нужны контейнеры IoC, у нас уже есть загрузчики модулей!

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

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

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

Подробнее об этом можно узнать в InversifyJS.

Ответ 11

Для 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 служба почтовой службы еще не существует. Используйте класс Reference, чтобы сообщить контейнеру, чтобы он ввел службу почтовой программы, когда он инициализирует диспетчера информационных бюллетеней:

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

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

container.compile()

Ответ 12

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

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

var cache = new Cache(mysqlStore);

Если вы не используете OOP в javascript, вы можете сделать функцию init, которая устанавливает все.

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

Ответ 13

Взгляните на провалы (простая, но мощная система управления зависимостями и инфраструктура управления сущностью (файлом) для Node.js)

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

Ответ 14

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

Ответ 15

Недавно я создал библиотеку под названием circuitbox, которая позволяет использовать инъекцию зависимостей с node.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;

Предположим, что в файле main.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 автоматически внедряет все компоненты, требуемые целевым компонентом, и предоставляет их вам для использования.

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

Надеюсь, что это поможет!

Ответ 16

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

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

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

  • Это помогает организовать и обосновать ваше приложение как слабосвязанные модули.

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

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

Ответ 17

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

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


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

Я написал статью об Injection Dependency в node.js.

Надеюсь, это поможет вам в этом.

Ответ 18

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

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

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

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

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

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

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

Ответ 19

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

Подробнее о KlarkJS

Краткий пример:

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

Ответ 20

Я обнаружил этот вопрос, пока отвечая на вопрос на моем собственном модуле 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.

Ответ 21

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

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