Dependency Injection vs Module Requiring
Since picking up a pet project and digging back through all of my old Javascript projects to remember how exactly this thing works I've uncovered a slightly deeper understanding which inevitably has led to confusion, questioning and plenty of reading.
The topic today is dependency injection vs requiring modules, which if I were working day to day with other developers I could easily find answered. Alas I have had to go to the various remote resources like Stackexchange, Medium, RisingStack and trusty Google, where more often than not I am met with out of date examples, biased views from 10 years ago and some nice flame wars around why the originators question is wrong from the start. I'm here to seek enlightenment from a community that is open to discussion.
TLDR; What is the preferred mechanism for pulling in dependencies from other modules within others.
My current project I have found that my "app.js" main file requires in a bunch of modules in order to knit together the hand off to lower down functionality which has dependencies on those top level modules. Typically this could be (I've simplified this massively):
const MQTTClient = require('./mqtt')
const Router = require('./router')
const client = MQTTClient .start('hostname')
const messageRouter = Router.init(client,{options})
client.on('message',messageRouter.handleMessage(topic, payload))
This means within the router I can always access the client methods and properties assigned from MQTT. I've found many posts suggesting this is good, I've found many posts suggesting this is bad. Ultimately though here are my impressions:
- This is potentially not even DI, to me this feels like I'm just passing a function which is an object as a parameter.
- Node/Javascript will cache the required module, so re-requiring it throughout my project is just referencing that cache.
- Testing within Router has been hugely easy. I find that creating a fake client object results in greater control over my tests.
- Functionally, there would have been no difference if the require MQTT sat within Router.js rather than my App main file, but the "single route in" to my App seems to be easier to understand.
- I dislike seeing a lot of "required" modules scattered around my project but at the same time, tracing back through function input and outputs to find the root of the injection could become time consuming on a large project.
- Javascript is not like other languages that rely heavily on DI. Module requiring is effectively another method of bringing objects into the scope you need.
- There seems to be a lot of DI JS libraries being produced and in fact you see these injections occurring in Express, Angular, Hapi etc all the time.
- People comment a lot about using a factory instead.
Thoughts and opinions?
Top comments (3)
I personally prefer using Partial Application as my "go-to" way of doing Dependecy Injection in Javascript.
A common misunderstanding is to say DI and be actually referring to IoC. Dependency injection is one of the ways to get inversion of control pattern applied on your app. Implementation details are easy: You receive already constructed interfaced instances as parameters in your constructors. Inversion of control is the pattern that we all know is good to have and it means resolving dependencies in some other place that is not the piece of code that actually uses the dependant component, this is how you low down your coupling (my favorite way to approach IoC is Property Resolution, but that's another discussion I'd guess).
I see NPM modules as a way to add plumbing code or frameworks (as your router, your MQTT or eventually a dependency injector π).
If you get to implement n-tiers on your project (e.g. controllers, builders, models, services, etc) IoC will be beneficial and will allow you to change implementations depending on the context (the typical example is mocking for unit tests and real implementations got production).
If what you are suggesting is to package your code in some sort of local npm registry and requiring them (removing the literals) that's something I'd like to hear how it ends.
Thanks βΊ
To follow up on these comments I've spent the last couple of days working through my code, refactoring my structure and in general just giving different methods a try. I've actually found a nice middle ground between injecting some dependencies to ensure loose coupling and easy testability vs hard locking things together with directly required modules.
Overall quite happy with the route I've gone. I can imagine in the future if my App were to grow and grow the benefits of an IoC and registering all of the dependencies in a central store.
For anyone looking for a good guide on all of this I forgot I had actually got a copy of "Node.js design patterns" which has a chapter specifically discussing all of this topic named "Wiring Modules". I've also noticed google and other searches react well to the same phrase.