5

I'm starting out a long term project, based on Node.js, and so I'm looking to build upon a solid dependency injection (DI) system.

Although Node.js at its core implies using simple module require()s for wiring components, I find this approach not best suited for a large project (e.g. requiring modules in each file is not that maintainable, testable or dynamic).

Now, I'd done my bits of research before posting this question and I've found out some interesting DI libraries for Node.js (see wire.js and dependable.js).

However, for maximal simplicity and minimal repetition I've come up with my own proposition of implementing DI:

  • You have a module, di.js, which acts as the container and is initialized by pointing to a JSON file storing a map of dependency names and their respective .js files. This already provides a dynamic nature to the DI, as you may easily swap test/development dependencies. The container can return dependencies by using an inject() function, which finds the dependency mapping and calls require() with it.

  • For simplicity, the module is assigned to a global variable, i.e. global.$di, so that any file in the project may use the container/injector by calling $di.inject().

Here's the gist of the implementation:

File di.js

module.exports = function(path) {

    this.deps = require(path);

    return {
        inject: function(name) {

                if (!deps[name])
                    throw new Error('dependency "' + name + '" isn\'t registered');
                return require(deps[name]);
        }
    };
};

Dependency map JSON file

{
    "vehicle": "lib/jetpack",
    "fuel": "lib/benzine",
    "octane": "lib/octane98"
}

Initialize the $di in the main JavaScript file, according to development/test mode:

var path = 'dep-map-' + process.env.NODE_ENV + '.json;
$di = require('di')(path);

Use it in some file:

var vehicle = $di.inject('vehicle');
vehicle.go();

So far, the only problem I could think of using this approach is the global variable $di. Supposedly, global variables are a bad practice, but it seems to me like I'm saving a lot of repetition for the cost of a single global variable.

What can be suggested against my proposal?

4
  • Why not just install or link your libraries inside of node_modules? Other than that, the only thing your "DI" function is giving you is aliasing. I also think it is a misnomer to call this dependency injection. You have to ask for it each time explicitly, that makes it "dependency extraction" if anything. Finally, $di won't be global - you'll need to do your require('di') in every file or assign it to your node globals explicitly. If you are planning to use Express, you might find express-di of interest. Maybe I am misunderstanding something. Commented Apr 14, 2014 at 4:48
  • putting all the libraries inside node_modules doesn't link them, unless you require a module explicitly. Also, it forces them to be in a single physical location, which is not a large project solution. i'm ok with explicitly declaring a depedency (not an implementation) at the top of a file, the same way you would do with function signatures in C# - so you do get dependency injection. lastly, the $di was tried and tested - it's global as it's not declared with "var" (you can also write global.$di, same effect) Commented Apr 14, 2014 at 6:20
  • This isn't really dependency injection. More like an object factory. If it were dependency injection, you wouldn't have to have a global $di object which everything depends on. You would have a setup portion of your code where things are wired together through their "constructors". Something more like var obj = require('some-module')(dependency1, dependency2); Commented Apr 14, 2014 at 7:19
  • @ChenR - I should have been more clear with what I meant by "link them" - check doc for npm link. It appears mihai cleared up the globals thing. As I look at it this morning, I do think an adapted version of the direction you're taking is fine for having an easy way to switch out implementations in a single place via your dependency map file. FWIW, one of the good/bad things about Node is you have a lot of flexibility - if the strategy works for you, start with it. If you need to refactor down the road so be it, but it may work great. Good luck! Commented Apr 14, 2014 at 13:45

2 Answers 2

1

Overall this approach sounds fine to me.

The way global variables work in Node.js is that when you declare a variable without the var keyword, and it gets added to the global object which is shared between all modules. You can also explicitly use global.varname. Example:

vehicle = "jetpack"
fuel = "benzine"
console.log(vehicle) // "jetpack"
console.log(global.fuel) // "benzine"

Variables declared with var will only be local to the module.

var vehicle = "car"
console.log(vehicle) // "car"
console.log(global.vehicle) // "jetpack"

So in your code if you are doing $di = require('di')(path) (without var), then you should be able to use it in other modules without any issues. Using global.$di might make the code more readable.

Sign up to request clarification or add additional context in comments.

4 Comments

so, in fact when i refer to a variable in a secondary module, which was declared in the main module, and that secondary module is required by the main module, the variable exists because i'm in the main module space. now i understand barry-johnson's comment on $di not being global (module level var instead) thanks
yea, that's pretty much it...the only way to pass data between modules is to put in module.exports
i have to comment on this once again, as it's been bothering me a bit - node.js does have a sense of global, "inter-module" space. if i declare "globalvar = 5; require('mod2');" in mod1.js (note the omitted var keyword) , and write "console.log(globalvar)" in mod2.js, the result would be 5, meaning that the internal module may depend on the external module
you are right, I edited the answer...disregard what I said so far
1

Your approach is a clear and simple one which is good. Whether you have a global variable or require your module every time is not important.

Regarding testability it allows you to replace your modules with mocks. For unit testing you should add a function that makes it easy for you to apply different mocks for each test. Something that extends your dependency map temporarily.

For further reading I can recommend a great blog article on dependency injection in Node.js as well as a talk on the future dependency injector of angular.js which is designed by some serious masterminds.

BTW, you might be interested in Fire Up! which is a dependency injection container I implemented.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.