在NodeJS中是否需要dependency injection,或者如何处理…?
我目前正在用nodejs创build一些实验项目。 我已经用Spring编写了很多的Java EE Web应用程序,并且欣赏了dependency injection的简易性。
现在我很好奇:我如何做与节点dependency injection? 或者:我甚至需要它吗? 有没有一个替代的概念,因为编程风格是不同的?
我在谈论简单的事情,比如共享一个数据库连接对象,到目前为止,但我还没有find一个满足我的解决scheme。
简而言之,您不需要像C#/ Java那样的dependency injection容器或服务定位器。 由于Node.js利用module pattern
,所以不需要执行构造函数或属性注入。 虽然你还可以。
JS的伟大之处在于你可以修改任何东西来实现你想要的东西。 这在testing时非常方便。
看看我非常蹩脚的做作的例子。
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的强大function。 请注意,我是一个CoffeeScript人,所以我的JS语法可能不正确的地方。 另外,我并不是说这是最好的方式,但它是一种方式。 Javascript的大师可能能够与其他解决scheme合作。
更新:
这应该解决您关于数据库连接的具体问题。 我会创build一个单独的模块来封装你的数据库连接逻辑。 像这样的东西:
MyDbConnection.js
:(确保select一个更好的名字)
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
模式来pipe理你的依赖关系。 希望这可以帮助更多。
require
是在Node.js中pipe理依赖关系的方式,当然它是直观和有效的,但也有其局限性。
我的build议是查看一下Node.js中的一些dependency injection容器,以了解它们的优缺点。 其中一些是:
- 分散 – 免责声明:我是作者
- 电解质 – 已经在这个线程中提到
- 线
- 静脉
- Pongular
仅举几例。
现在真正的问题是,与一个简单的require
相比,你能用Node.js DI容器实现什么?
优点:
- 更好的可testing性:模块接受它们的依赖作为input
- 控制反转:决定如何连接你的模块而不接触你的应用程序的主代码。
- 用于parsing模块的可定制algorithm:依赖关系具有“虚拟”标识符,通常它们不绑定到文件系统上的path。
- 更好的可扩展性:由IoC和“虚拟”标识符启用。
- 其他花哨的东西可能:
- asynchronous初始化
- 模块生命周期pipe理
- DI容器本身的可扩展性
- 可以轻松实现更高级别的抽象(例如AOP)
缺点:
- 不同于Node.js的“体验”:不
require
使用肯定会感觉你正在偏离Node的思维方式。 - 依赖与其实现之间的关系并不总是明确的。 依赖关系可以在运行时解决,并受各种参数的影响。 代码变得更难理解和debugging
- 启动时间较慢
- 成熟度(目前):目前没有一款解决scheme真的很受欢迎,所以没有太多的教程,没有生态系统,没有经过testing。
- 一些DI容器在Browserify和Webpack等模块打包程序中效果不佳。
与软件开发相关的任何事情一样,在DI之间require
select还是取决于您的要求,系统复杂性和编程风格。
我也写了一个模块来完成这个,它被称为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 eg 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)注入的启发,但采用了不同的方法。 我不使用vm
来评估testing模块,实际上我使用节点自己的要求。 这样你的模块就像使用require()
(除了你的修改)。 也完全支持debugging。
我知道这个线程在这个时候还比较老,但是我想我会在这个问题上留下一些想法。 TL; DR是由于JavaScript的无typesdynamic本质,实际上可以做很多事情,而不依赖于dependency injection(DI)模式或使用DI框架。 但是,随着应用程序越来越大,越来越复杂,DI可以确保您的代码的可维护性。
DI在C#
为了理解为什么DI在JavaScript中不是一个大的需求,看看像C#这样的强types语言是很有帮助的。 (对那些不认识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(); } }
用这种方式编写代码几乎没有问题。
-
Car
类与Horn
类中喇叭的特定实现紧密耦合。 如果我们想改变汽车使用的喇叭的types,即使喇叭的使用不变,我们也必须修改Car
类。 这也使得testing变得困难,因为我们无法独立于它的依赖项Horn
类来testingCar
类。 -
Car
类负责Horn
类的生命周期。 在这样一个简单的例子中,这不是一个大问题,但是在实际的应用程序中,依赖关系会依赖于依赖关系,依赖关系等等Car
类需要负责创build整个依赖关系树。 这不仅是复杂而重复的,而且是违反了class级的“单一责任”。 它应该把重点放在汽车上,而不是创造实例。 - 没有办法重用相同的依赖项实例。 再次,这在玩具应用中并不重要,但考虑一个数据库连接。 您通常会在应用程序中共享单个实例。
现在,让我们重构这个使用dependency injection模式。
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
取出喇叭实例化,并将其传入。 这解决了上述问题,并将其留给应用程序的主要function来pipe理特定实例及其生命周期。
这意味着可以在不碰Car
类的情况下引入一种新型号angular:
class FrenchHorn : IHorn { public void Honk() { Console.WriteLine("le beep!"); } }
主要可以注入FrenchHorn
类的一个实例。 这也极大地简化了testing。 你可以创build一个MockHorn
类来注入Car
构造函数,以确保你正在独立testingCar
类。
上面的例子显示了手动dependency injection。 通常,DI是通过一个框架完成的(例如,C#世界中的Unity或Ninject )。 这些框架将通过走你的依赖关系图并根据需要创build实例来为你做所有的依赖布线。
标准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是无types的,所以我们没有像之前那么紧密的耦合。 不需要接口(也不存在),因为car
模块只是试图在horn
模块输出的任何地方调用honk
方法。
另外,因为Node require
caching所有东西,所以模块基本上是存储在一个容器中的单例。 任何其他在horn
模块上执行require
的模块都将得到完全相同的实例。 这使得像数据库连接这样的单一对象共享非常容易。
现在还有一个问题,就是car
模块负责获取自己的依赖horn
。 如果你想让汽车在喇叭中使用不同的模块,你必须改变car
模块中的require
语句。 这不是一个很常见的事情,但它确实会导致testing问题。
人们处理testing问题的通常方法是使用proxyquire 。 由于JavaScript的dynamic特性,proxyquire会拦截调用来请求并返回您提供的任何存根/模拟。
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非常灵活。 如果您对上述方法不满意,则可以使用dependency injection模式编写模块。 在这个模式中,每个模块都会导出一个工厂函数(或者一个类的构造函数)。
// 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
模块负责实例的生命周期和布线。 unit testing非常简单,因为您可以将模拟/存根传递给函数。 再次,如果这是足够好的为您的应用程序去与它。
Bolus DI框架
与C#不同,没有build立标准的DI框架来帮助你的依赖pipe理。 npmregistry中有许多框架,但没有一个被广泛采用。 其他答案中已经提到了其中许多选项。
我对任何可用的选项都不是特别满意,所以我写了我自己的名为bolus 。 Bolus被devise为与上面的DI风格编写的代码一起工作,并试图非常干燥 ,非常简单。 使用上面完全相同的car.js
和horn.js
模块,可以用bolus重写index.js
模块,如下所示:
// index.js var Injector = require("bolus"); var injector = new Injector(); injector.registerPath("**/*.js"); var car = injector.resolve("car"); car.honkHorn();
基本的想法是你创build一个注入器。 您将所有模块注册到注射器中。 然后你简单地解决你所需要的。 Bolus将遍历依赖关系图并根据需要创build并注入依赖关系。 在这样的玩具例子中,你不会节省很多,但是在具有复杂依赖树的大型应用程序中,储蓄是巨大的。
Bolus支持一些漂亮的特性,比如可选的依赖和testing全局,但是我看到了与标准Node.js方法相关的两个主要优点。 首先,如果你有很多类似的应用程序,你可以创build一个专用的npm模块来创build一个注入器,并在其上注册有用的对象。 然后,您的特定应用程序可以根据需要添加,覆盖和解决,就像AngularJS的注入器的工作方式一样。 其次,您可以使用bolus来pipe理各种依赖关系。 例如,您可以使用中间件为每个请求创build一个子注入器,并根据这些注册模块在注入器上注册用户ID,会话ID,logging器等。 然后解决你需要服务的请求。 这样可以为每个请求提供模块的实例,并防止将logging器等传递给每个模块函数调用。
我为此build立了电解质 。 其他的dependency injection解决scheme对我的口味来说太过于侵入,而对全球require
混乱则是我的一个特别的抱怨。
电解质包含模块,特别是那些导出Connect / Express中间件中看到的“设置”function的模块。 实质上,这些types的模块只是他们返回的对象的工厂。
例如,创build数据库连接的模块:
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' ];
您在底部看到的是注释 ,电解质用来实例化和注入依赖关系的额外元数据,自动将应用程序的组件连接在一起。
要创build数据库连接:
var db = electrolyte.create('database');
电解质传递遍历@require
的依赖关系,并将实例注入到导出的函数中。
关键是这是微创。 这个模块是完全可用的,独立于电解质本身。 这意味着你的unit testing只能testing被测模块 ,传入模拟对象,而不需要额外的依赖关系来重新连接内部。
当运行完整的应用程序时,电解质在模块级别上进行,将所有东西连接在一起,而不需要全局,单例或过多的pipe道。
我最近检查这个线程的原因与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');
如果没有别名在加载程序中 – 那么它将默认为常规require。 这有两个好处:我可以在任何版本的类中进行交换,并且不需要在整个应用程序中使用相对path名(所以如果我需要在当前文件下面或上面的自定义库,我不需要遍历,并要求将caching模块对同一个键)。 它也允许我在应用程序的任何一点指定mock,而不是直接的testing套件。
为了方便,我刚刚发布了一个npm模块:
我自己看着这个。 我不喜欢引入魔法依赖utils库提供机制劫持我的模块导入。 相反,我为我的团队提出了一个“devise指南”,通过在模块中引入工厂函数导出,明确指出哪些依赖可以被模拟。
我大量使用ES6的参数和解构function,以避免一些样板,并提供一个命名的依赖覆盖机制。
这里是一个例子:
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的语法。
我一直喜欢IoC概念的简单性 – “你不需要知道任何关于环境的知识,在需要的时候你会被某个人叫来”
但是我所看到的所有IoC实现完全相反 – 他们把代码混乱得比没有代码更多。 所以,我创build了自己的IoC,按照我的愿望工作 – 它保持隐藏,隐藏90%的时间 。
它用于MonoJS Web框架http://monojs.org
我在谈论简单的事情,比如共享一个数据库连接对象,到目前为止,但我还没有find一个满足我的解决scheme。
它是这样做的 – 在configuration中注册一次组件。
app.register 'db', -> require('mongodb').connect config.dbPath
并在任何地方使用它
app.db.findSomething()
你可以看到完整的组件定义代码(带有数据库连接和其他组件) https://github.com/sinizinairina/mono/blob/master/mono.coffee
当你必须告诉IoC该做什么时,这是唯一的地方,之后所有这些组件将被自动创build和连线,而且不必在应用程序中再看到IoC特定的代码。
我认为我们仍然需要Nodejs中的dependency injection,因为它放松了服务之间的依赖关系,使应用程序更清晰。
受Spring Framework的启发,我也实现了自己的模块来支持Nodejs中的dependency injection。 我的模块也能够检测code changes
并auto reload
服务,而无需重新启动您的应用程序。
访问我的项目: Buncha – IoC容器
谢谢!
事实上,你可以在没有IoC容器的情况下testing你的node.js,因为JavaScript是一个非常dynamic的编程语言,你可以在运行时修改几乎所有的东西。
考虑以下几点:
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)支持许多用例:
- 内核模块
- 内核中间件
- 使用类,string文字或符号作为依赖标识符
- 注入常数值
- 注入类的构造函数
- 注塑工厂
- 汽车厂
- 注入提供者(asynchronous工厂)
- 激活处理程序(用于注入代理)
- 多注射
- 标记的绑定
- 自定义标记装饰器
- 命名的绑定
- 上下文绑定
- 友好的例外(例如循环依赖)
您可以在InversifyJS上了解更多信息。
这取决于你的应用程序的devise。 你显然可以做一个类似于java的注入,你可以像这样在构造函数中传递依赖,创build一个类的对象。
function Cache(store) { this._store = store; } var cache = new Cache(mysqlStore);
如果你不是在JavaScript做OOP,你可以做一个初始化函数来设置一切。
但是,还有另一种方法可以在基于事件的系统(如node.js)中更常见。 如果你可以模拟你的应用程序(大部分时间)只对事件采取行动,那么你所需要做的就是设置所有的东西(我通常通过调用一个init函数来完成),并从一个存根发出事件。 这使得testing相当容易和可读。
看一下dips(Node.js的一个简单而强大的dependency injection和实体(文件)pipe理框架)
Google的di.js在nodejs(+ browser)(+ ES6)
对于ES6,我开发了这个容器https://github.com/zazoomauro/node-dependency-injection
import {ContainerBuilder} from 'node-dependency-injection' let container = new ContainerBuilder() container.register('mailer', 'Mailer')
那么你可以设置,例如,在容器中的运输select:
import {ContainerBuilder} from 'node-dependency-injection' let container = new ContainerBuilder() container .register('mailer', 'Mailer') .addArgument('sendmail')
这个类现在更灵活了,因为您已经将运输的select从实施中分离出来并放入容器中。
现在,邮件服务在容器中,您可以将其注入为其他类的依赖项。 如果您有像这样的NewsletterManager类:
class NewsletterManager { construct (mailer, fs) { this._mailer = mailer this._fs = fs } } export default NewsletterManager
定义newsletter_manager服务时,邮件服务还不存在。 使用Reference类来告诉容器在初始化通讯pipe理器时注入邮件服务:
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文件等configuration文件来设置容器
服务容器可以由于各种原因被编译。 这些原因包括检查任何潜在的问题,如循环引用,使容器更有效。
container.compile()
我最近创build了一个名为circuitbox的库,允许你在node.js中使用dependency injection。 它实现了真正的dependency injection与我所见过的许多依赖查找库。 Circuitbox还支持asynchronous创build和初始化例程。 下面是一个例子:
假设以下代码位于名为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 lets you define your components and declare their dependencies as modules. Once its initialized, it allows you to retrieve a component. Circuitbox automatically injects all the components the target component requires and gives it to you for use.
The project is in alpha version. Your comments, ideas and feedback are welcome.
希望它有帮助!
I think other posts have done a great job in the argument for using DI. For me the reasons are
-
Inject dependencies without knowing their paths. This means that if you change a module location on disk or swap it with another, you don't need to touch every file that depends on it.
-
It makes it a lot easier to mock dependencies for testing without the pain of overriding the global
require
function in a way that works without problems. -
It helps you organize and reason about you application as loosely coupled modules.
But I had a really hard time finding a DI framework that my team and I can easily adopt. So I recently built a framework called deppie based on these features
- Minimal API that can be learned in a few minutes
- No extra code/config/annotations required
- One to one direct mapping to
require
modules - Can be adopted partially to work with existing code
It should be flexible and simple like this:
var MyClass1 = function () {} var MyClass2 = function (myService1) { // myService1.should.be.instanceof(MyClass1); } container.register('myService1', MyClass1); container.register('myService2', MyClass2, ['myService1']);
I have written article about Dependency Injection in node.js.
- Manage your services —node.js dependency injection
- Package documentation here
I hope it can help you with this.
Node.js requires DI as much as any other platform. If you are building something big, DI will make it easier to mock the dependencies of your code and test your code thoroughly.
Your database layer modules for example, shouldn't just get required at your business code modules because, when unit testing these business code modules, the daos will load and connect to the database.
One solution would be to pass the dependencies as module parameters:
module.exports = function (dep1, dep2) { // private methods return { // public methods test: function(){...} } }
This way dependencies can be mocked easily and naturally and you can stay focused on testing your code, without using any tricky 3rd party library.
There are other solutions out there (broadway, architect etc) which can help you with this. although they may do more than what you want or use more clutter.
I have developed a library that handles the dependency injection with a simple way, that decreases the boilerplate code. Each module is defined by a unique name and a controller function. The parameters of the controller reflects the module's dependencies.
Read more on KlarkJS
简单的例子:
KlarkModule(module, 'myModuleName1', function($nodeModule1, myModuleName2) { return { log: function() { console.log('Hello from module myModuleName1') } }; });
-
myModuleName1
is the name of the module. -
$nodeModule1
is an external library fromnode_module
. The name resolves tonode-module1
. The prefix$
indicates that it is an external module. -
myModuleName2
is the name of an internal module. - The return value of the controller is used from the other internal modules, when they define the parameter
myModuleName1
.
I discovered this question while answering to an issue on my own DI module asking why one would ever need a DI system for NodeJS programming.
The answer was clearly tending to the ones given in this thread: it depends. There are trade-offs for both approaches and reading this question's answers give a good shape of them.
So, the real answer to this question, should be that in some situations, you would use a DI system, in others not.
That said, what you want as a developer is to not repeat yourself and reuse your services across your various applications.
This means that we should write services that are ready to be used in DI system but not tied to DI libraries. To me, it means that we should write services like this:
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 }
That way your service works not matter if you use it with or without a DI tool.
I worked with .Net, PHP and Java for long time so I wanted to have a convenient Dependency Injection in NodeJS too. People said the built-in DI in NodeJS is enough as we can get it with Module. But it didn't satisfy me well. I wanted to keep a Module no more than a Class. Additionally, I wanted the DI to have a full support for Module life cycle management (singleton module, transient module etc.) but with Node module, I had to write manual code very often. Lastly, I wanted to make Unit Test easier. That's why I created a Dependency Injection for myself.
If you are looking for a DI, give it a try. It can be found here: https://github.com/robo-creative/nodejs-robo-container . It's fully documented. It also addresses some common problems with DI and how to solve them in OOP way. 希望它有帮助。