我目前正在用nodejs创建一些实验性项目。我用Spring编写过很多Java EE web应用程序,非常欣赏依赖注入的便利性。
现在我很好奇:如何对节点进行依赖注入?或者:我真的需要它吗?因为编程风格不同,是否存在替代概念?
到目前为止,我谈论的是一些简单的事情,比如共享数据库连接对象,但我还没有找到一个让我满意的解决方案。
我目前正在用nodejs创建一些实验性项目。我用Spring编写过很多Java EE web应用程序,非常欣赏依赖注入的便利性。
现在我很好奇:如何对节点进行依赖注入?或者:我真的需要它吗?因为编程风格不同,是否存在替代概念?
到目前为止,我谈论的是一些简单的事情,比如共享数据库连接对象,但我还没有找到一个让我满意的解决方案。
当前回答
我是在回答我自己的DI模块上的一个问题时发现这个问题的,这个问题是为什么有人需要一个DI系统来进行NodeJS编程。
答案显然倾向于这篇文章中给出的答案:这要看情况。这两种方法都有折衷之处,阅读这个问题的答案可以让你更好地了解它们。
所以,这个问题的真正答案应该是,在某些情况下,你会使用依赖注入系统,在其他情况下则不会。
也就是说,作为开发人员,您希望的是不要在各种应用程序中重复使用您的服务。
这意味着我们应该编写一些可以在依赖注入系统中使用但不绑定到依赖注入库的服务。对我来说,这意味着我们应该像这样编写服务:
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的论证方面做得很好。对我来说,原因是
注入依赖项而不知道它们的路径。这意味着如果您更改磁盘上的模块位置或将其与另一个模块交换,则不需要触及依赖于该模块的每个文件。 它使模拟依赖关系以进行测试变得更加容易,而不用痛苦地重写全局require函数,而且不会出现任何问题。 它可以帮助您将应用程序组织为松散耦合的模块。
但是我很难找到一个我和我的团队可以轻松采用的依赖注入框架。所以我最近基于这些特性构建了一个名为deppie的框架
可以在几分钟内学会的最小API 不需要额外的代码/配置/注释 一对一直接映射需要的模块 可以部分地使用现有代码吗
事实上,你可以在没有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上了解更多。
为了很好地测试应用程序,大多数时候最好使用反转控制工具在运行/测试时注入所需的对象。 所以最好不要在模块中直接使用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();
}
Node.js和其他平台一样需要DI。如果您正在构建一些大的东西,DI将使您更容易模拟代码的依赖关系并彻底测试代码。
例如,数据库层模块不应该只在业务代码模块中使用,因为在单元测试这些业务代码模块时,dao将加载并连接到数据库。
一种解决方案是将依赖项作为模块参数传递:
module.exports = function (dep1, dep2) {
// private methods
return {
// public methods
test: function(){...}
}
}
通过这种方式,依赖关系可以轻松自然地模拟,你可以专注于测试你的代码,而不需要使用任何棘手的第三方库。
还有其他的解决方案(百老汇,建筑师等)可以帮助你解决这个问题。尽管他们可能做的比你想要的多,或者使用更多的杂物。
require()和最近的ES模块(import)是管理Node.js中的依赖关系的方法,当然它是直观和有效的,但它也有其局限性。
我的建议是看一看目前Node.js中可用的依赖注入容器,了解它们的优缺点。其中一些是:
awilix injection-js bottlejs inversify node-dependency-injection
举几个例子。
现在真正的问题是,与简单的require()或import相比,使用Node.js DI容器可以实现什么?
优点:
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)
缺点:
与Node.js的“体验”不同:使用DI绝对感觉你偏离了Node的思维方式。 依赖项与其实现之间的关系并不总是明确的。依赖项可能在运行时被解析,并受到各种参数的影响。代码变得更加难以理解和调试 启动时间较慢 大多数DI容器不能很好地与Browserify和Webpack等模块捆绑器一起使用。
与软件开发相关的任何事情一样,在DI或require()/import之间的选择取决于您的需求、系统复杂性和编程风格。