我是否需要在NodeJS中注入依赖项,或者如何处理…?

我目前正在用nodejs创建一些实验性项目。我用Spring编写过很多Java EE web应用程序,非常欣赏依赖注入的便利性。

现在我很好奇:如何对节点进行依赖注入?或者:我真的需要它吗?因为编程风格不同,是否存在替代概念?

到目前为止,我谈论的是一些简单的事情,比如共享数据库连接对象,但我还没有找到一个让我满意的解决方案。

138902 次浏览

这取决于应用程序的设计。显然,你可以做一个类似java的注入,在那里创建一个类的对象,并将依赖项传递给构造函数,就像这样。

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


var cache = new Cache(mysqlStore);

如果你不是在javascript中做OOP,你可以做一个init函数来设置一切。

然而,还有另一种方法可以采用,这种方法在基于事件的系统(如node.js)中更为常见。如果您可以将应用程序建模为仅(大多数时候)对事件进行操作,那么您所需要做的就是设置所有内容(我通常通过调用init函数来完成)并从存根发出事件。这使得测试相当容易和易读。

简而言之,您不需要像在c# /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 of 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大师们或许能提供其他解决方案。

更新:

这应该可以解决您关于数据库连接的特定问题。我将创建一个单独的模块来封装数据库连接逻辑。就像这样:

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模式来管理你的依赖项。希望这对你们有帮助。

我还写了一个模块来完成这个任务,它叫做重新连接。只需使用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!
});

我受到内森·麦金尼斯的注射剂的启发,但使用了不同的方法。我没有使用vm来计算测试模块,实际上我使用node自己的require。这样你的模块就像使用require()一样(除了你的修改)。此外,完全支持调试。

我最近检查了这个线程,原因和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...
};

然后,不要直接调用require…

var myDatabaseService = loader.load('dataBaseService');
如果加载器中没有别名,那么它将默认为常规require。 这样做有两个好处:我可以交换类的任何版本,并且消除了这种需要 在整个应用程序中使用相对路径名(因此如果我需要下面的自定义库 或以上的当前文件,我不需要遍历,并要求将缓存模块针对相同的关键)。它还允许我在应用程序的任何地方指定模拟,而不是在立即的测试套件中

为了方便起见,我刚刚发布了一个小的npm模块:

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

我构建电解液就是为了这个目的。其他依赖注入解决方案对我来说太有侵入性了,而弄乱全局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' ];

你在底部看到的是注释,这是一个额外的元数据,电解质使用它来实例化和注入依赖项,自动将应用程序的组件连接在一起。

创建一个数据库连接。

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

电解质传递遍历__abc0d依赖项,并将实例作为参数注入导出函数。

关键是这是微创的。这个模块是完全可用的,独立于电解质本身。这意味着你的单元测试可以测试只有被测试的模块,传入模拟对象,而不需要额外的依赖关系来重新连接内部。

当运行完整的应用程序时,电解质在模块间级别介入,将东西连接在一起,而不需要全局变量、单例或过多的管道。

require()和最近的ES模块(import)是管理Node.js中的依赖项的方式,当然它是直观和有效的,但它也有其局限性。

我的建议是看一看目前Node.js中可用的依赖注入容器,了解它们的优缺点。其中一些是:

举几个例子。

现在真正的问题是,与简单的require()import相比,你能用Node.js DI容器实现什么?

优点:

  • 更好的可测试性:模块接受它们的依赖项作为输入
  • 控制反转:决定如何在不影响应用程序主代码的情况下连接模块。
  • 用于解析模块的可定制算法:依赖项有“虚拟”;标识符,通常不绑定到文件系统上的路径。
  • 更好的可扩展性:由IoC和“virtual”支持;标识符。
  • 其他可能的花哨的东西:
    • 异步的初始化
    • 模块生命周期管理
    • DI容器本身的可扩展性
    • 可以轻松地实现更高层次的抽象(例如AOP)

缺点:

  • 与Node.js的“经验”不同:使用DI肯定会让人感觉你偏离了Node的思维方式。
  • 依赖项与其实现之间的关系并不总是明确的。依赖项可能在运行时被解析,并受到各种参数的影响。代码变得更加难以理解和调试
  • 启动时间较慢
  • 大多数DI容器不能很好地与Browserify和Webpack等模块捆绑器一起使用。

就像任何与软件开发相关的东西一样,在DI或require()/import之间的选择取决于您的需求、系统复杂性和编程风格。

我最近创建了一个名为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自动注入目标组件所需的所有组件,并将其提供给您使用。

该项目处于alpha版本。欢迎您的评论、想法和反馈。

希望能有所帮助!

我一直很喜欢IoC的简单理念——“你不需要了解任何环境,需要的时候有人会叫你。”

但是我看到的所有IoC实现都完全相反——它们用更多的东西使代码变得混乱。因此,我创建了自己的IoC,它可以像我希望的那样工作——它90%的时间都隐藏不可见

它用于MonoJS web框架http://monojs.org

我说的是简单的事情,比如共享一个数据库连接对象,所以

.我没有找到一个让我满意的解决办法

它是这样做的——在配置中注册组件一次。

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

看看dips(一个简单而强大的Node.js依赖注入和实体(文件)管理框架)

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

我自己调查过了。我不喜欢引入神奇的依赖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的人,请原谅它的语法。

我知道这个帖子在这一点上是相当老的,但我想我会在这个问题上发表我的想法。TL;DR是由于JavaScript的无类型、动态特性,您实际上可以在不依赖依赖注入(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类的生命周期。在这样一个简单的例子中,这不是一个大问题,但在真正的应用程序中,依赖会有依赖,依赖会有依赖,等等。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类编码到接口,而不是特定的实现。现在代码可以接受任何实现IHorn的东西。其次,我们从Car中取出喇叭实例化,并将其传入。这解决了上述问题,并将其留给应用程序的主要功能来管理特定的实例及其生命周期。

这意味着我们可以在不涉及Car类的情况下为汽车引入一种新的喇叭类型:

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

main可以只注入FrenchHorn类的实例。这也极大地简化了测试。你可以创建一个MockHorn类注入到Car构造函数中,以确保你只是在孤立地测试Car类。

上面的例子展示了手动依赖注入。通常DI是通过框架完成的(例如c#世界中的团结Ninject)。这些框架将通过遍历依赖关系图并根据需要创建实例来为您完成所有依赖关系连接。

标准的Node.js方式

现在让我们看看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模块只会尝试在horn模块导出的任何内容上调用honk方法。

此外,由于Node的require缓存了所有内容,模块本质上是存储在容器中的单例。在horn模块上执行require的任何其他模块都将获得完全相同的实例。这使得共享单例对象(如数据库连接)非常容易。

现在仍然存在一个问题,car模块负责获取它自己的依赖horn。如果你想让汽车的喇叭使用不同的模块,你必须改变car模块中的require语句。这不是一件很常见的事情,但它确实会导致测试问题。

通常人们处理测试问题的方法是proxyquire。由于JavaScript的动态特性,proxyquire会拦截需要的调用,并返回您提供的存根/mock。

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中的DI

Node.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模块负责实例生命周期和连接。单元测试非常简单,因为您只需将mock /存根传递给函数。同样,如果这对您的应用程序来说足够好,那么就使用它。

Bolus DI框架

与c#不同的是,没有现成的标准依赖注入框架来帮助你管理依赖项。npm注册表中有许多框架,但没有一个被广泛采用。这些选项中的许多已经在其他答案中被引用。

我对任何可用的选项都不是特别满意,所以我写了自己的。Bolus被设计为使用上面的DI风格编写的代码,并试图非常和非常简单。使用上面完全相同的car.jshorn.js模块,你可以用bolus重写index.js模块:

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


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

基本思想是创建一个注入器。你在注入器中注册了所有的模块。然后你只需解决你所需要的。Bolus将遍历依赖关系图,并根据需要创建和注入依赖关系。在这样一个简单的例子中,您不会节省太多,但是在具有复杂依赖树的大型应用程序中,节省的时间是巨大的。

Bolus支持许多漂亮的特性,如可选依赖项和测试全局变量,但相对于标准Node.js方法,我看到了两个关键的好处。首先,如果你有很多类似的应用程序,你可以为你的基础库创建一个私有的npm模块,它创建一个注入器并在上面注册有用的对象。然后,您的特定应用程序可以根据需要添加、覆盖和解析,就像AngularJS的注入器的工作方式一样。其次,您可以使用bolus来管理依赖关系的各种上下文。例如,你可以使用中间件为每个请求创建一个子注入器,在注入器上注册用户id、会话id、日志记录器等,以及依赖这些的任何模块。然后确定服务请求需要什么。这为每个请求提供了模块实例,避免了必须将记录器等传递给每个模块函数调用。

我认为我们仍然需要Nodejs中的依赖注入,因为它放松了服务之间的依赖关系,使应用程序更加清晰。

受到Spring框架的启发,我也实现了自己的模块来支持Nodejs中的依赖注入。我的模块也能够检测code changesauto reload的服务,而无需重新启动应用程序。

访问我的项目:Buncha - IoC容器

谢谢你!

我认为其他的文章在使用DI的论证方面做得很好。对我来说,原因是

  1. 注入依赖项而不知道它们的路径。这意味着如果您更改磁盘上的模块位置或将其与另一个模块交换,则不需要触及依赖于该模块的每个文件。

  2. 它使模拟依赖关系以进行测试变得更加容易,而不用痛苦地重写全局require函数,以一种没有问题的方式工作。

  3. 它可以帮助您将应用程序组织为松散耦合的模块。

但是我很难找到一个我和我的团队可以轻松采用的依赖注入框架。所以我最近基于这些特性建立了一个叫做deppie的框架

  • 可以在几分钟内学会的最小API
  • 不需要额外的代码/配置/注释
  • 一对一直接映射到require模块
  • 可以部分地使用现有代码吗

事实上,你可以在没有IoC容器的情况下测试node.js,因为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:JavaScript中没有IoC容器的位置

误解2:我们不需要IoC容器,我们已经有模块加载器了!

误解3:依赖倒置===注入依赖

如果你也喜欢使用IoC容器的想法,你可以看看InversifyJS。最新版本(2.0.0)支持多种用例:

  • 内核模块
  • 内核的中间件
  • 使用类、字符串或符号作为依赖标识符
  • 常数值注入
  • 类构造函数的注入
  • 工厂注入
  • 汽车工厂
  • 提供程序的注入(异步工厂)
  • 激活处理程序(用于注入代理)
  • 多注射
  • 标记绑定
  • 自定义标记装饰器
  • 指定绑定
  • 上下文绑定
  • 友好的异常(例如循环依赖)

你可以在InversifyJS上了解更多。

它应该像这样灵活和简单:

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




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

我写过一篇关于node.js中的依赖注入的文章。

我希望它能帮到你。

Node.js和其他平台一样需要DI。如果您正在构建一些大的东西,DI将使您更容易模拟代码的依赖关系并彻底测试代码。

例如,数据库层模块不应该只在业务代码模块中使用,因为在单元测试这些业务代码模块时,dao将加载并连接到数据库。

一种解决方案是将依赖项作为模块参数传递:

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是模块的名称。
  • $nodeModule1node_module的外部库。名称解析为node-module1。前缀$表示它是一个外部模块。
  • myModuleName2是内部模块的名称。
  • 控制器的返回值从其他内部模块中使用,当它们定义参数myModuleName1时。
对于ES6,我开发了这个容器 https://github.com/zazoomauro/node-dependency-injection < / p >
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')

这个类现在灵活得多,因为您已经将传输的选择从实现中分离出来,并将其放入容器中。

现在邮件服务已经在容器中,您可以将其作为其他类的依赖项注入。如果你有一个这样的newsletter ttermanager类:

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


export default NewsletterManager

在定义newsletter tter_manager服务时,邮件服务还不存在。使用Reference类告诉容器在初始化通讯管理器时注入mailer服务:

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

我在自己的DI模块上回答问题时发现了这个问题,我在问为什么需要一个NodeJS编程的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
}

这样你的服务就可以工作了,不管你是用还是用

我使用。net, PHP和Java工作了很长时间,所以我想在NodeJS中也有一个方便的依赖注入。人们说NodeJS中内置的DI已经足够了,因为我们可以通过Module得到它。但它并没有让我很满意。我想保持一个模块不超过一个类。此外,我希望DI能够完全支持模块生命周期管理(单模块、瞬态模块等),但对于Node模块,我不得不经常手动编写代码。最后,我想让单元测试更简单。这就是为什么我为自己创建了一个依赖注入。

如果您正在寻找DI,请尝试一下。它可以在这里找到:https://github.com/robo-creative/nodejs-robo-container。它有完整的文档。并对依赖注入中常见的问题进行了分析,并提出了面向对象的解决方法。希望能有所帮助。

TypeDI是这里提到的所有代码中最甜蜜的,看看TypeDI中的代码

import "reflect-metadata";
import {Service, Container} from "typedi";


@Service()
class SomeClass {


someMethod() {
}


}


let someClass = Container.get(SomeClass);
someClass.someMethod();

看看这段代码:

import {Container, Service, Inject} from "typedi";


// somewhere in your global app parameters
Container.set("authorization-token", "RVT9rVjSVN");


@Service()
class UserRepository {


@Inject("authorization-token")
authorizationToken: string;


}
为了很好地测试应用程序,大多数时候最好使用反转控制工具在运行/测试时注入所需的对象。 所以最好不要在模块中直接使用require或import。相反,调用DI容器来获取所需的对象 如果你不想使用第三方库,你可以通过创建一个自定义DI容器来模仿IoC工具的行为。 在测试的时候,你可以模拟你的DI容器,并注入你的假模块用于测试。 下面是一个自定义DI容器的例子,这个版本的容器不支持接口

myDependecy.js

const myDependecy = {};
export default myDependecy;


myDependecy.myTestFunction = () => {
console.log("this is as test function.");
};

diContainer.js

import myDependecy from "./myDependecy.js";


const diContainer = {};
export default diContainer;


diContainer.myDependecy = myDependecy;

myModule.js

import diContainer from "./diContainer.js";


function myFunction() {
diContainer.myDependecy.myTestFunction();
}