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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9250851/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 15:10:35  来源:igfitidea点击:

Do I need dependency injection in NodeJS, or how to deal with ...?

node.jsdependency-injectioninversion-of-control

提问by Erik

I currently creating some experimental projects with nodejs. I have programmed a lot Java EE web applications with Spring and appreciated the ease of dependency injection there.

我目前正在使用 nodejs 创建一些实验项目。我已经使用 Spring 编写了很多 Java EE Web 应用程序,并且很欣赏那里的依赖注入的易用性。

Now I am curious: How do I do dependency injection with node? Or: Do I even need it? Is there a replacing concept, because the programming style is different?

现在我很好奇:如何使用节点进行依赖注入?或者:我什至需要它吗?有没有替代的概念,因为编程风格不同?

I am talking about simple things, like sharing a database connection object, so far, but I have not found a solution that satisfies me.

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

采纳答案by JP Richardson

In short, you don't need a dependency injection container or service locater like you would in C#/Java. Since Node.js, leverages the module pattern, it's not necessary to perform constructor or property injection. Although you still can.

简而言之,您不需要像在 C#/Java 中那样的依赖注入容器或服务定位器。由于 Node.js 利用了module pattern,因此没有必要执行构造函数或属性注入。虽然你仍然可以。

The great thing about JS is that you can modify just about anything to achieve what you want. This comes in handy when it comes to testing.

JS 的伟大之处在于你可以修改任何东西来实现你想要的。这在测试时派上用场。

Behold my very lame contrived example.

看我非常蹩脚的人为例子。

MyClass.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:

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

Notice how MyClassdepends upon the fsmodule? As @ShatyemShekhar mentioned, you can indeed do constructor or property injection as in other languages. But it's not necessary in Javascript.

注意如何MyClass取决于fs模块?正如@ShatyemShekhar 所提到的,您确实可以像在其他语言中一样进行构造函数或属性注入。但在 Javascript 中没有必要。

In this case, you can do two things.

在这种情况下,您可以做两件事。

You can stub the fs.readdirSyncmethod or you can return an entirely different module when you call require.

您可以存根该fs.readdirSync方法,也可以在调用时返回一个完全不同的模块require

Method 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;

Method 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);

}

The key is to leverage the power of Node.js and Javascript. Note, I'm a CoffeeScript guy, so my JS syntax might be incorrect somewhere. Also, I'm not saying that this is the best way, but it is a way. Javascript gurus might be able to chime in with other solutions.

关键是要利用 Node.js 和 Javascript 的强大功能。请注意,我是一个 CoffeeScript 人,所以我的 JS 语法可能在某处不正确。另外,我并不是说这是最好的方法,但它是一种方法。Javascript 专家可能会与其他解决方案配合。

Update:

更新:

This should address your specific question regarding database connections. I'd create a separate module for your to encapsulate your database connection logic. Something like this:

这应该解决您关于数据库连接的具体问题。我会为您创建一个单独的模块来封装您的数据库连接逻辑。像这样的东西:

MyDbConnection.js: (be sure to choose a better name)

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    
}

Then, any module that needs a database connection would then just include your MyDbConnectionmodule.

然后,任何需要数据库连接的MyDbConnection模块都将只包含您的模块。

SuperCoolWebApp.js:

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. 

Do not follow this example verbatim. It's a lame example at trying to communicate that you leverage the modulepattern to manage your dependencies. Hopefully this helps a bit more.

不要逐字逐句地遵循这个例子。这是一个试图传达您利用该module模式来管理您的依赖项的蹩脚示例。希望这会有所帮助。

回答by Mario

requireis theway of managing dependencies in Node.js and surely it is intuitive and effective, but it has also its limitations.

require是Node.js 中管理依赖项方式,当然它直观有效,但也有其局限性。

My advice is to take a look at some of the Dependency Injection containers available today for Node.js to have an idea on what are their pros/cons. Some of them are:

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

Just to name a few.

仅举几个。

Now the real question is, what can you achieve with a Node.js DI container, compared to a simple require?

现在真正的问题是,与简单的require.

Pros:

优点:

  • better testability: modules accepts their dependencies as input
  • Inversion of Control: decide how to wire your modules without touching the main code of your application.
  • a customizable algorithm for resolving modules: dependencies have "virtual" identifiers, usually they are not bound to a path on the filesystem.
  • Better extensibility: enabled by IoC and "virtual" identifiers.
  • Other fancy stuff possible:
    • Async initialization
    • Module lifecycle management
    • Extensibility of the DI container itself
    • Can easily implement higher level abstractions (e.g. AOP)
  • 更好的可测试性:模块接受它们的依赖作为输入
  • 控制反转:决定如何在不触及应用程序主要代码的情况下连接模块。
  • 用于解析模块的可定制算法:依赖项具有“虚拟”标识符,通常它们不绑定到文件系统上的路径。
  • 更好的可扩展性:由 IoC 和“虚拟”标识符启用。
  • 其他可能的花哨的东西:
    • 异步初始化
    • 模块生命周期管理
    • DI 容器本身的可扩展性
    • 可以轻松实现更高级别的抽象(例如 AOP)

Cons:

缺点:

  • Different from the Node.js "experience": not using requiredefinitely feels like you are deviating from the Node way of thinking.
  • The relationship between a dependency and its implementation is not always explicit. A dependency may be resolved at runtime and influenced by various parameters. The code becomes more difficult to understand and debug
  • Slower startup time
  • Maturity (at the moment): none of the current solutions is reallypopular at the moment, so not so many tutorials, no ecosystem, not battle tested.
  • Some DI containers will not play well with module bundlers like Browserify and Webpack.
  • 与Node.js的“体验”不同:不使用require肯定感觉是在偏离Node的思维方式。
  • 依赖项与其实现之间的关系并不总是明确的。依赖关系可以在运行时解决并受各种参数的影响。代码变得更难理解和调试
  • 启动时间较慢
  • 成熟度(目前):目前没有一个解决方案是真正流行的,所以没有那么多教程,没有生态系统,也没有经过实战测试。
  • 某些 DI 容器与 Browserify 和 Webpack 等模块捆绑器不能很好地配合使用。

As with anything related to software development, choosing between DI or requiredepends on your requirements, your system complexity, and your programming style.

与软件开发相关的任何事情一样,在 DI 或 DI 之间进行选择require取决于您的要求、您的系统复杂性和您的编程风格。

回答by Dave Johnson

I know this thread is fairly old at this point, but I figured I'd chime in with my thoughts on this. The TL;DR is that due to the untyped, dynamic nature of JavaScript, you can actually do quite a lot without resorting to the dependency injection (DI) pattern or using a DI framework. However, as an application grows larger and more complex, DI can definitely help the maintainability of your code.

我知道这个线程在这一点上已经很老了,但我想我会同意我的想法。TL;DR 是由于 JavaScript 的无类型、动态特性,您实际上可以做很多事情,而无需求助于依赖注入 (DI) 模式或使用 DI 框架。但是,随着应用程序变得越来越大和越来越复杂,DI 绝对可以帮助您提高代码的可维护性。

DI in C#

C#中的DI

To understand why DI isn't as big of a need in JavaScript, it's helpful to look at a strongly typed language like C#. (Apologies to those who don't know C#, but it should be easy enough to follow.) Say we have an app that describes a car and its horn. You would define two classes:

要理解为什么 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();
    }
}

There are few issues with writing the code this way.

以这种方式编写代码几乎没有问题。

  1. The Carclass is tightly coupled to the particular implementation of the horn in the Hornclass. If we want to change the type of horn used by the car, we have to modify the Carclass even though its usage of the horn doesn't change. This also makes testing difficult because we can't test the Carclass in isolation from its dependency, the Hornclass.
  2. The Carclass is responsible for the lifecycle of the Hornclass. In a simple example like this it's not a big issue, but in real applications dependencies will have dependencies, which will have dependencies, etc. The Carclass would need to be responsible for creating the entire tree of its dependencies. This is not only complicated and repetitive, but it violates the "single responsibility" of the class. It should focus on being a car, not creating instances.
  3. There is no way to reuse the same dependency instances. Again, this isn't important in this toy application, but consider a database connection. You would typically have a single instance that is shared across your application.
  1. 所述Car类被紧密耦合到特定的实施方式中的喇叭的Horn类。如果我们想改变汽车使用的喇叭类型,我们必须修改Car类,即使它的喇叭使用没有改变。这也使测试变得困难,因为我们无法Car独立于其依赖项类来测试Horn类。
  2. Car班负责的生命周期Horn类。在像这样的简单示例中,这不是什么大问题,但在实际应用程序中,依赖项将具有依赖项,这些依赖项将具有依赖项等。Car该类需要负责创建其依赖项的整个树。这不仅复杂且重复,而且违反了类的“单一职责”。它应该专注于成为一辆汽车,而不是创建实例。
  3. 无法重用相同的依赖项实例。同样,这在这个玩具应用程序中并不重要,但请考虑数据库连接。您通常会有一个在您的应用程序中共享的实例。

Now, let's refactor this to use a dependency injection pattern.

现在,让我们重构它以使用依赖注入模式。

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

We've done two key things here. First, we've introduced an interface that our Hornclass implements. This lets us code the Carclass to the interface instead of the particular implementation. Now the code could take anything that implements IHorn. Second, we've taken the horn instantiation out of Carand pass it in instead. This resolves the issues above and leaves it to the application's main function to manage the specific instances and their lifecycles.

我们在这里做了两件关键的事情。首先,我们引入了一个我们的Horn类实现的接口。这让我们可以将Car类编码到接口而不是特定的实现。现在代码可以采用任何实现IHorn. 其次,我们将喇叭实例化取出Car并传入。这解决了上述问题,并将其留给应用程序的主要功能来管理特定实例及其生命周期。

What this means is that would could introduce a new type of horn for the car to use without touching the Carclass:

这意味着可以在不接触Car班级的情况下为汽车引入一种新型喇叭:

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

The main could just inject an instance of the FrenchHornclass instead. This also dramatically simplifies testing. You could create a MockHornclass to inject into the Carconstructor to ensure you are testing just the Carclass in isolation.

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

The example above shows manual dependency injection. Typically DI is done with a framework (e.g. Unityor Ninjectin the C# world). These frameworks will do all of the dependency wiring for you by walking your dependency graph and creating instances as needed.

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

The Standard Node.js Way

标准的 Node.js 方式

Now let's look at the same example in Node.js. We would probably break our code into 3 modules:

现在让我们看看 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();

Because JavaScript is untyped, we don't have the quite the same tight coupling that we had before. There is no need for interfaces (nor do they exist) as the carmodule will just attempt to call the honkmethod on whatever the hornmodule exports.

因为 JavaScript 是无类型的,所以我们没有以前那样的紧密耦合。不需要接口(它们也不存在),因为car模块只会尝试honkhorn模块导出的任何内容上调用该方法。

Additionally, because Node's requirecaches everything, modules are essentially singletons stored in a container. Any other module that performs a requireon the hornmodule will get the exact same instance. This makes sharing singleton objects like database connections very easy.

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

Now there is still the issue that the carmodule is responsible for fetching its own dependency horn. If you wanted the car to use a different module for its horn, you'd have to change the requirestatement in the carmodule. This is not a very common thing to do, but it does cause issues with testing.

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

The usual way people handle the testing problem is with proxyquire. Owing to the dynamic nature of JavaScript, proxyquire intercepts calls to require and returns any stubs/mocks you provide instead.

人们处理测试问题的常用方法是使用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...

This is more than enough for most applications. If it works for your app then go with it. However, in my experience as applications grow larger and more complex, maintaining code like this becomes harder.

这对于大多数应用程序来说已经足够了。如果它适用于您的应用程序,请使用它。然而,根据我的经验,随着应用程序变得越来越大和越来越复杂,维护这样的代码变得更加困难。

DI in JavaScript

JavaScript 中的 DI

Node.js is very flexible. If you aren't satisfied with the method above, you can write your modules using the dependency injection pattern. In this pattern, every module exports a factory function (or a class constructor).

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

This is very much analogous to the C# method earlier in that the index.jsmodule is responsible for instance lifecycles and wiring. Unit testing is quite simple as you can just pass in mocks/stubs to the functions. Again, if this is good enough for your application go with it.

这与之前的 C# 方法非常相似,因为index.js模块负责实例生命周期和接线。单元测试非常简单,因为您可以将模拟/存根传递给函数。同样,如果这对您的应用程序来说足够好,那就去吧。

Bolus DI Framework

大剂量 DI 框架

Unlike C#, there are no established standard DI frameworks to help with your dependency management. There are a number of frameworks in the npm registry but none have widespread adoption. Many of these options have been cited already in the other answers.

与 C# 不同,没有既定的标准 DI 框架来帮助您进行依赖项管理。npm 注册表中有许多框架,但没有一个被广泛采用。其中许多选项已在其他答案中引用。

I wasn't particularly happy with any of the options available so I wrote my own called bolus. Bolus is designed to work with code written in the DI style above and tries to be very DRYand very simple. Using the exact same car.jsand horn.jsmodules above, you can rewrite the index.jsmodule with bolus as:

我对任何可用的选项都不是特别满意,所以我写了我自己的名为bolus。Bolus 旨在处理以上述 DI 风格编写的代码,并尝试非常DRY和非常简单。使用car.jshorn.js上面完全相同的模块,您可以将index.js带有 bolus的模块重写为:

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

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

The basic idea is that you create an injector. You register all of your modules in the injector. Then you simply resolve what you need. Bolus will walk the dependency graph and create and inject dependencies as needed. You don't save much in a toy example like this, but in large applications with complicated dependency trees the savings are huge.

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

Bolus supports a bunch of nifty features like optional dependencies and test globals, but there are two key benefits I've seen relative to the standard Node.js approach. First, if you have a lot of similar applications, you can create a private npm module for your base that creates an injector and registers useful objects on it. Then your specific apps can add, override, and resolve as needed much like how AngularJS'sinjector works. Second, you can use bolus to manage various contexts of dependencies. For example, you could use middleware to create a child injector per request, register the user id, session id, logger, etc. on the injector along with any modules depending on those. Then resolve what you need to serve requests. This gives you instances of your modules per request and prevents having to pass the logger, etc. along to every module function call.

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

回答by Johannes Ewald

I've also written a module to accomplish this, it's called rewire. Just use npm install rewireand then:

我还编写了一个模块来实现这一点,它称为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!
});

I've been inspired by Nathan MacInnes's injectrbut used a different approach. I don't use vmto eval the test-module, in fact I use node's own require. This way your module behaves exactly like using require()(except your modifications). Also debugging is fully supported.

我受到Nathan MacInnes 的注入器的启发,但使用了不同的方法。我不用vm来评估测试模块,实际上我使用节点自己的要求。这样你的模块的行为就像 using require()(除了你的修改)。还完全支持调试。

回答by Jared Hanson

I built Electrolytefor just this purpose. The other dependency injection solutions out there were too invasive for my tastes, and messing with the global requireis a particular grievance of mine.

我为此目的构建了Electrolyte。其他依赖注入解决方案对我的口味来说太具有侵略性了,而弄乱全局require是我的一个特别的不满。

Electrolyte embraces modules, specifically those that export a "setup" function like you see in Connect/Express middleware. Essentially, these types of modules are just factories for some object they return.

Electrolyte 包含模块,特别是那些导出“设置”功能的模块,就像您在 Connect/Express 中间件中看到的那样。本质上,这些类型的模块只是它们返回的某些对象的工厂。

For example, a module that creates a database connection:

例如,一个创建数据库连接的模块:

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

What you see at the bottom are annotations, an extra bit of metadata that Electrolyte uses to instantiate and inject dependencies, automatically wiring your application's components together.

您在底部看到的是annotations,这是 Electrolyte 用于实例化和注入依赖项的额外元数据,自动将应用程序的组件连接在一起。

To create a database connection:

要创建数据库连接:

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

Electrolyte transitively traverses the @require'd dependencies, and injects instances as arguments to the exported function.

Electrolyte 传递性地遍历@require'd 依赖项,并将实例作为参数注入到导出的函数中。

The key is that this is minimally invasive. This module is completely usable, independent of Electrolyte itself. That means your unit tests can test just the module under test, passing in mock objects without need for additional dependencies to rewire internals.

关键是这是微创的。该模块完全可用,独立于电解质本身。这意味着您的单元测试可以测试被测模块,传入模拟对象,而无需额外的依赖项来重新连接内部结构。

When running the full application, Electrolyte steps in at the inter-module level, wiring things together without the need for globals, singletons or excessive plumbing.

在运行完整的应用程序时,Electrolyte 会在模块间级别介入,将事物连接在一起,而无需全局变量、单例或过多的管道。

回答by ctrlplusb

I looked into this myself. I dislike introducing magic dependency utils libraries which provide mechanisms to hiHyman my module imports. Instead I came up with a "design guideline" for my team to rather explicitly state what dependencies can be mocked by introducing a factory function export within my modules.

我自己研究过这个。我不喜欢引入魔法依赖工具库,这些库提供了劫持我的模块导入的机制。相反,我为我的团队提出了一个“设计指南”,通过在我的模块中引入工厂函数导出来明确说明可以模拟哪些依赖项。

I make extensive use of ES6 features for parameters and destructuring in order to avoid some boilerplate and provide a named dependency override mechanism.

我广泛使用 ES6 的参数和解构特性,以避免一些样板文件并提供命名的依赖覆盖机制。

Here is an example:

下面是一个例子:

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

And here is an example of it's usage

这是它的用法示例

import { factory } from './bar';

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

Excuse the ES6 syntax for those unfamiliar with it.

请原谅那些不熟悉 ES6 语法的人。

回答by sunwukung

I recently checked this thread for much the same reason as the OP - most of the libs I've encountered temporarily rewrite the require statement. I've had mixed degrees of success with this method, and so I ended up using the following approach.

我最近出于与 OP 大致相同的原因检查了该线程 - 我遇到的大多数库都临时重写了 require 语句。我用这种方法取得了不同程度的成功,所以我最终使用了以下方法。

In the context of an express application - I wrap app.js in a bootstrap.js file:

在快速应用程序的上下文中 - 我将 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();

The object map passed to the loader looks like this:

传递给加载器的对象映射如下所示:

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

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

Then, rather than directly calling require...

然后,而不是直接调用 require ...

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

If no alias is located in the loader - then it will just default to a regular require. This has two benefits: I can swap in any version of the class, and it remove the need to use relative path names throughout the application (so If I need a custom lib below or above the current file, I don't need to traverse, and require will cache the module against the same key). It also allows me to specify mocks at any point in the app, rather than in the immediate test suite.

如果加载器中没有别名 - 那么它只会默认为常规要求。这有两个好处:我可以交换任何版本的类,并且不需要在整个应用程序中使用相对路径名(所以如果我需要在当前文件下方或上方的自定义库,我不需要遍历,并且 require 将根据相同的键缓存模块)。它还允许我在应用程序中的任何位置指定模拟,而不是在直接测试套件中。

I've just published a little npm module for convenience:

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

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

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

回答by Remo H. Jansen

The reality is that you can test your node.js without IoC container because JavaScript is a really dynamic programming language and you can modify almost everything at run-time.

现实情况是,您可以在没有 IoC 容器的情况下测试您的 node.js,因为 JavaScript 是一种真正的动态编程语言,您几乎可以在运行时修改所有内容。

Consider the following:

考虑以下:

import UserRepository from "./dal/user_repository";

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

export default UserController;

So you can override the coupling between components at run-time. I like to think that we should aim to decouple our JavaScript modules.

因此您可以在运行时覆盖组件之间的耦合。我认为我们应该以解耦 JavaScript 模块为目标。

The only way to achieve real decoupling is by removing the reference to the UserRepository:

实现真正解耦的唯一方法是删除对 的引用UserRepository

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

export default UserController;

This means that somewhere else you will need to do the object composition:

这意味着您需要在其他地方进行对象组合:

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

export default new UserController(new UserRepository());

I like the idea of delegating the object composition to an IoC container. You can learn more about this idea in the article The current state of dependency inversion in JavaScript. The article tries to debunk some "JavaScript IoC container myths":

我喜欢将对象组合委托给 IoC 容器的想法。您可以在文章JavaScript 中的依赖倒置的当前状态 中了解有关此想法的更多信息。这篇文章试图揭穿一些“JavaScript IoC 容器神话”:

Myth 1: There is no place for IoC containers in JavaScript

Myth 2: We don't need IoC containers, we already have module loaders!

Myth 3: Dependency inversion === injecting dependencies

误解 1:JavaScript 中没有 IoC 容器的位置

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

误区三:依赖倒置 === 注入依赖

If you also like the idea of using an IoC container you could take a look to InversifyJS. The latest release (2.0.0) supports many use cases:

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

  • Kernel modules
  • Kernel middleware
  • Use classes, string literals or Symbols as dependency identifiers
  • Injection of constant values
  • Injection of class constructors
  • Injection of factories
  • Auto factory
  • Injection of providers (async factory)
  • Activation handlers (used to inject proxies)
  • Multi injections
  • Tagged bindings
  • Custom tag decorators
  • Named bindings
  • Contextual bindings
  • Friendly exceptions (e.g. Circular dependencies)
  • 内核模块
  • 内核中间件
  • 使用类、字符串文字或符号作为依赖标识符
  • 注入常​​数值
  • 注入类构造函数
  • 工厂注塑
  • 汽车厂
  • 提供程序的注入(异步工厂)
  • 激活处理程序(用于注入代理)
  • 多次注射
  • 标记绑定
  • 自定义标签装饰器
  • 命名绑定
  • 上下文绑定
  • 友好异常(例如循环依赖)

You can learn more about it at InversifyJS.

您可以在InversifyJS 中了解更多信息。

回答by Mauro

For ES6 I developed this container https://github.com/zazoomauro/node-dependency-injection

对于 ES6,我开发了这个容器 https://github.com/zazoomauro/node-dependency-injection

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

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

Then you can set, for example, the choice of transport in the container:

然后你可以设置,例如,容器中的传输选择:

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

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

This class is now much more flexible as you have separated the choice of transport out of the implementation and into the container.

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

Now that the mailer service is in the container you can inject it as a dependency of other classes. If you have a NewsletterManager class like this:

现在邮件程序服务在容器中,您可以将其作为其他类的依赖项注入。如果您有这样的 NewsletterManager 类:

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

export default NewsletterManager

When defining the newsletter_manager service, the mailer service does not exist yet. Use the Reference class to tell the container to inject the mailer service when it initializes the newsletter manager:

在定义 newsletter_manager 服务时,mailer 服务还不存在。使用 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'))

You can also setting up the container with configuration files like Yaml, Json or JS files

您还可以使用 Yaml、Json 或 JS 文件等配置文件设置容器

The service container can be compiled for various reasons. These reasons include checking for any potential issues such as circular references and making the container more efficient.

可以出于各种原因编译服务容器。这些原因包括检查任何潜在的问题,例如循环引用和提高容器的效率。

container.compile()

回答by Tho

I think we still need Dependency Injection in Nodejs because it loosens the dependencies between services and make application clearer.

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

Inspired by Spring Framework, I also implement my own module to support dependency injection in Nodejs. My module is also able to detect the code changesand auto reloadthe services without restart your application.

Spring Framework 的启发,我也实现了自己的模块来支持 Nodejs 中的依赖注入。我的模块还能够在不重新启动应用程序的情况下检测code changesauto reload服务。

Visit my project at: Buncha - IoC container

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

Thank you!

谢谢!