Javascript依赖注入节点中的DIP:需要与构造器注入 [英] Javascript dependency injection & DIP in node: require vs constructor injection

查看:121
本文介绍了Javascript依赖注入节点中的DIP:需要与构造器注入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我刚刚从.NET世界开始NodeJs开发
我正在网页上搜索在Javascript中重新使用DI / DIP的最佳做法

I'm new to NodeJs development coming from the .NET world i'm searching the web for best practices regrading DI / DIP in Javascript

在.NET中,我将在构造函数中声明我的依赖关系,而在javascript中,我看到一个常见的模式是通过require语句来声明模块级别的依赖。

In .NET i would declare my dependencies at the constructor whereas in javascript i see a common pattern is to declare dependencies in the module level via a require statement.

看起来像这样,当我使用需要我耦合到一个特定的文件,而使用构造函数来接收我的依赖更灵活。

for me it looks like that when i use require i'm coupled to a specific file while using a constructor to receive my dependency is more flexible.

你会推荐做什么作为JavaScript中的最佳实践?(我正在寻找架构模式而不是IOC技术解决方案)

What would you recommend doing as a best practice in javascript? (I'm looking for the architectural pattern and not an IOC technical solution)

搜索网络我来到这个博客帖子(在评论中有一些非常有趣的讨论):
https ://blog.risingstack.com/dependency-injection-in-node-js/

searching the web i came along this blog post (which has some very interesting discussion in the comments): https://blog.risingstack.com/dependency-injection-in-node-js/

它使我的冲突不错
这里是博客帖子中的一些代码,让您了解我在说什么:

it summerizes my conflict pretty good. here's some code from the blog post to make you understand what i'm talking about:

// team.js
var User = require('./user');

function getTeam(teamId) {  
  return User.find({teamId: teamId});
}

module.exports.getTeam = getTeam; 

一个简单的测试看起来像这样:

A simple test would look something like this:

 // team.spec.js
    var Team = require('./team');  
    var User = require('./user');

    describe('Team', function() {  
      it('#getTeam', function* () {
        var users = [{id: 1, id: 2}];

        this.sandbox.stub(User, 'find', function() {
          return Promise.resolve(users);
        });

        var team = yield team.getTeam();

        expect(team).to.eql(users);
      });
    });

VS DI:

// team.js
function Team(options) {  
  this.options = options;
}

Team.prototype.getTeam = function(teamId) {  
  return this.options.User.find({teamId: teamId})
}

function create(options) {  
  return new Team(options);
}

测试:

// team.spec.js
var Team = require('./team');

describe('Team', function() {  
  it('#getTeam', function* () {
    var users = [{id: 1, id: 2}];

    var fakeUser = {
      find: function() {
        return Promise.resolve(users);
      }
    };

    var team = Team.create({
      User: fakeUser
    });

    var team = yield team.getTeam();

    expect(team).to.eql(users);
  });
});


推荐答案

关于你的问题:我不认为那里是JS社区的一个惯例。我已经看到这两种类型在野外,需要修改(如 rewire proxyquire )和构造器注入(通常使用专用的DI容器)。但是,我个人认为,不要使用DI容器,更适合JS。这是因为JS是一种动态语言,作为一流公民的功能。让我解释一下:

Regarding your question: I don't think that there is a common practice in the JS community. I've seen both types in the wild, require modifications (like rewire or proxyquire) and constructor injection (often using a dedicated DI container). However, personally, I think not using a DI container is a better fit with JS. And that's because JS is a dynamic language with functions as first-class citizens. Let me explain that:

使用DI容器强制构造器注入一切。由于两个主要原因造成巨大的配置开销:

Using DI containers enforce constructor injection for everything. It creates a huge configuration overhead for two main reasons:


  1. 在单元测试中提供模拟

  2. 创建

关于第一个参数:我不会调整我的代码用于我的单元测试。如果它使您的代码更清洁,更简单,更多功能更少,更容易出错,然后出去。但是,如果你唯一的原因是你的单位测试,我不会采取权衡。您可以通过需要修改和猴子修补获得相当的远景。如果你发现自己写的太多嘲笑,你应该不会写一个单元测试,而是一个集成测试。 Eric Elliott撰写了一篇伟大的文章关于这个问题。

Regarding the first argument: I would not adjust my code just for my unit tests. If it makes your code cleaner, simpler, more versatile and less error-prone, then go for out. But if your only reason is your unit test, I would not take the trade-off. You can get pretty far with require modifications and monkey patching. And if you find yourself writing too many mocks, you should probably not write a unit test at all, but an integration test. Eric Elliott has written a great article about this problem.

关于第二个参数:这是一个有效的参数。如果要创建一个只关心接口的组件,而不是关于实际的实现,我将选择一个简单的构造函数注入。但是,由于JS并没有强制你使用类的一切,为什么不使用函数?

Regarding the second argument: This is a valid argument. If you want to create a component that only cares about an interface, but not about the actual implementation, I would opt for a simple constructor injection. However, since JS does not force you to use classes for everything, why not just use functions?

功能编程,将状态IO与实际处理分离是一个常见的范例。例如,如果您正在编写一个应该在文件夹中计算文件类型的代码,那么可以编写这个代码(特别是当他/她来自使用各种语言的语言时):

In functional programming, separating stateful IO from actual processing is a common paradigm. For instance, if you're writing code that is supposed to count file types in a folder, one could write this (especially when he/she is coming from a language that enforce classes everywhere):

const fs = require("fs");

class FileTypeCounter {
    countFileTypes(dirname, callback) {
        fs.readdir(dirname, function (err) {
            if (err) return callback(err);
            // recursively walk all folders and count file types
            // ...
            callback(null, fileTypes);
        });
    }
}

现在,如果你想测试,你需要更改你的代码以注入假的 fs module:

Now if you want to test that, you need to change your code in order to inject a fake fs module:

class FileTypeCounter {
    constructor(fs) {
        this.fs = fs;
    }
    countFileTypes(dirname, callback) {
        this.fs.readdir(dirname, function (err) {
            // ...
        });
    }
}

现在,所有使用你的课程的人都需要注入 fs 到构造函数中。由于这是无聊的,使您的代码更复杂,一旦你有长的依赖关系图,开发人员发明了DI容器,在那里他们可以配置的东西,DI容器的实例化。

Now, everyone who is using your class needs to inject fs into the constructor. Since this is boring and makes your code more complicated once you have long dependency graphs, developers invented DI containers where they can just configure stuff and the DI container figures out the instantiation.

然而,只是编写纯函数呢?

However, what about just writing pure functions?

function fileTypeCounter(allFiles) {
    // count file types
    return fileTypes;
}

function getAllFilesInDir(dirname, callback) {
    // recursively walk all folders and collect all files
    // ...
    callback(null, allFiles);
}

// now let's compose both functions
function getAllFileTypesInDir(dirname, callback) {
    getAllFilesInDir(dirname, (err, allFiles) => {
        callback(err, !err && fileTypeCounter(allFiles));
    });
}

现在,您已经有两个功能齐全的超功能功能,一个正在做IO,另一个处理数据。 fileTypeCounter 是一个纯功能,容易测试。 getAllFilesInDir 是不纯的,但这是一个常见的任务,你经常会发现它已经在 npm 其他人为此编写了集成测试。 getAllFileTypesInDir 只需使用一点控制流即可组成您的功能。这是您想要确保整个应用程序正常工作的集成测试的典型案例。

Now you have two super-versatile functions out-of-the-box, one which is doing IO and the other one which processes the data. fileTypeCounter is a pure function and super-easy to test. getAllFilesInDir is impure but a such a common task, you'll often find it already on npm where other people have written integration tests for it. getAllFileTypesInDir just composes your functions with a little bit of control flow. This is a typical case for an integration test where you want to make sure that your whole application is working correctly.

通过将IO和数据处理之间的代码分开,您赢得了没有必要注入任何东西。如果你不需要注入任何东西,那就是一个好兆头。纯功能是测试最简单的方法,仍然是在项目之间共享代码的最简单方法。

By separating your code between IO and data processing, you won't find the need to inject anything at all. And if you don't need to inject anything, that's a good sign. Pure functions are the easiest thing to test and are still the easiest way to share code between projects.

这篇关于Javascript依赖注入节点中的DIP:需要与构造器注入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆