-
-
Save domenic/5753428 to your computer and use it in GitHub Desktop.
"use strict"; | |
// Domenic needs a Tweeter | |
function Domenic(tweeter) { | |
this.tweeter = tweeter; | |
} | |
Domenic.inject = ["tweeter"]; | |
Domenic.prototype.doSomethingCool = function () { | |
return this.tweeter.tweet("Did something cool!"); | |
}; | |
module.exports = Domenic; |
"use strict"; | |
// Merick also needs a tweeter | |
function Merrick(tweeter) { | |
this.tweeter = tweeter; | |
} | |
Merrick.inject = ["tweeter"]; | |
Merrick.prototype.doSomethingAwesome = function () { | |
return this.tweeter.tweet("Did something awesome!"); | |
}; | |
module.exports = Merrick; |
"use strict"; | |
function App(domenic, merrick) { | |
this.domenic = domenic; | |
this.merrick = merrick; | |
} | |
App.inject = ["domenic", "merrick"]; | |
App.prototype.run = function () { | |
this.domenic.doSomethingCool().done(); | |
this.merrick.doSomethingAwesome().done(); | |
}; | |
module.exports = App; |
"use strict"; | |
var diContainer = require("di-container"); | |
// Declaratively wire up dependencies. Note that while Domenic and Merrick both need the "tweeter" | |
// abstraction, we can choose *via configuration* to give them a different "tweeter" concretion. | |
// They are completely decoupled from this knowledge. | |
diContainer.config({ | |
"app": require("./3-App"), | |
"domenic": { | |
constructor: require("./1-Domenic"), | |
inject: { | |
"tweeter": require("./LolSpeakTweeter-not-shown") | |
} | |
}, | |
"merrick": { | |
constructor: require("./2-Merrick"), | |
inject: { | |
"tweeter": require("./LeetSpeekTweeter-not-shown") | |
} | |
} | |
}); | |
// Construct the entire object graph, using above declarative config. | |
// This is the *only* time you should ever use `diContainer.get`. | |
diContainer.get("app").run(); | |
// Your framework might use `diContainer.get` itself, e.g. for convention-based lookups. | |
// But you never should. | |
// Should Tweet: | |
// - "LOL I HAZ DID SOMETHING COOL LOL!" | |
// - "1 d1d s0m3th1ng 4w3s0m3!" |
"use strict"; | |
// You can also of course wire up the graph manually. | |
// That's still doing dependency injection. | |
var Domenic = require("./1-Domenic"); | |
var Merrick = require("./2-Merrick"); | |
var App = require("./3-App"); | |
var LolSpeakTweeter = require("./LolSpeakTweeter-not-shown"); | |
var LeetSpeekTweeter = require("./LeetSpeekTweeter-not-shown"); | |
var app = new App( | |
new Domenic(new LolSpeakTweeter()), | |
new Merrick(new LeetSpeakTweeter()) | |
); | |
app.run(); |
@jmreidy I think Intravenous might fit the bill. We tried using it on a project once that was just using "raw DI" (i.e. manually compose everything in the composition root). We almost made it work, but retrofitting the auto-factory stuff onto what we were doing didn't end up going so well, and then I left the company, so, welp.
@jmreidy might want to check out node-di too, think it's written by one of the angular guys.
@domenic How do you decide what to inject and what to leverage the module system for (via require or import)? I struggle with this because it seems like using 100% DI might not make sense at times and there might be a rule of thumb to go by.
For example, assume we have a module that needs a db client, mocking is not a big deal because we want our tests to actually run against our db instance, so let's not use testability as a reason to favor one over the other...
We could do it with no DI and leverage our trusty module system:
var db = require('db');
function Foo(){
this.client = db.createClient();
}
module.exports = Foo;
var Foo = require('./foo');
var foo = new Foo();
or
We could leave it open for DI:
function Bar(client){
this.client = client;
}
module.exports = Bar;
var db = require('db');
var Bar = require('./bar');
var bar = new Bar(db.createClient());
The latter of the two is clean and maybe once we throw db client
configuration options into the mix it starts to make more sense to favor it. However, sometimes I feel like DI in these cases can be a leaky abstraction, meaning consumers of Bar
will need to know how it works internally. For example, perhaps Bar
will call a blocking operation on the client
or close the connection on the client
, now the consumer needs to know to pass Bar
it's own client
instance instead of one that is perhaps being shared elsewhere in our application.
Any thoughts/advice/whatever would be much appreciated!
I really believe that if you're using CommonJS modules already (with Browserify for example) you don't really need a DI container.
In order to prove it, I have rewritten the Coffee Maker example found in this video https://www.youtube.com/watch?v=_OGGsf1ZXMs#t=121
https://github.com/royriojas/coffe-maker
Basically you can just use the injectr
approach of provide a second parameter to the require
function.
This second parameter is the list of modules to be mocked when required inside the test. This means require is tampered in your testing environment.
So using the same file as the one you provided like:
var db = require('db');
function Foo(){
this.client = db.createClient();
}
module.exports = Foo;
In testing env
var mockDB = {};
var Foo = require('./foo', {
'db': {
createClient: function () {
return mockDB; // mocked client object;
}
}
})
var foo = new Foo();
expect(foo.client).to.equal(mockDB);
And that's it, now you can write your tests with ease.
I know for sure that DI can do way more things that just replace a mock during testing, but I would say that the vast majority of javascript projects just need to reuse the code during testing. So this will work.
Using this approach I was also able to switch implementations on runtime, that is a bit more complicated and involves playing with browserify to require/export modules, to make them available outside the bundle, but it is totally possible. So far I have not found a single feature provided by a DI container to make me thing we need one in Javascript.
I have provided a demo of how to do this with Karma and publish the code for be consumed in a browser.
I know also that ES6 modules are coming, I would rather prefer CommonJS format, but let's see what happens. It is good to have more options...
Do you know of any JS DI frameworks that work in this fashion?
Everything I've found just inspects the constructor arguments, and pulls out dependency mappings from argument names. That's all well and good, except for client-side code that you're obfuscating / minifying.
Your example here uses an explicit
Fn.inject
mapping, as does Angular's implementation. (Angular's implementation would probably be fantastic if it were generalized into its own lib.)