Dependency Injection is one form of Inversion of Control technique that supports the Dependency Inversion principle (the last one of SOLID Principles – decoupling software modules). In this tutorial, we’re gonna look at way to implement Dependency Injection pattern in Node.js.
Contents
Dependency Injection Overview
What is the problem with tightly coupled modules?
We may end up hardwiring multiple modules. It’s difficult to change a module later, so we may need to refactor a lot of code.
=> How we can avoid writing highly cohesive and tighly coupled modules?
The Dependency Inversion principle said that:
So, how we understand this sentence:
– Modules don’t connect to each other directly, but using interfaces/references to the modules. High-level module will use low-level module via that interface/reference. We call low-level module “dependency”.
– Dependency is instantiated before being passed (or injected) to other modules as parameters.
We can implement Dependency Injection in Node.js by 2 ways:
– Constructor Injection: Dependency is injected to a class via constructor method of the class. This is the common way.
– Setter Injection: Dependency is injected to a class via setter method of the class.
For example, we use config()
method as the setter method to inject logger
to AnyClass
:
class AnyClass { config({ logger }) { this.logger = logger; } doSomething(amount) { this.logger.write(...); // ... } // ... } module.exports = new AnyClass(); |
Then, if we want to use a logger
, we initiate it before injecting:
const AnyThing = require('./AnyClass'); // const logger = require('./logger-console'); const logger = require('./logger-file'); AnyThing.config({ logger }); |
Now we can use AnyThing
without caring about logger
details. This provides us a loose-coupling, reusable modules with different dependencies (logger-console
or logger-file
).
Implement Dependency Injection in Node.js
Inject Dependency
We create a Bank
class that has config()
method (setter method) with property called logger
, logger
is like an interface to plug-in any logger module.
Bank.js
class Bank { constructor() { this.cash = 0; } config({ logger }) { this.logger = logger; } deposit(amount) { this.cash += amount; if (this.logger) { this.logger.write(`deposit: ${amount}, current cash: ${this.cash}`); } return this.cash; } withdraw(amount) { if (amount <= this.cash) { this.cash -= amount; if (this.logger) { this.logger.write(`withdraw: ${amount}, current cash: ${this.cash}`); } return true; } else { if (this.logger) { this.logger.write('failed to withdraw!'); } return false; } } total() { if (this.logger) { this.logger.write(`check cash: ${this.cash}`); } return this.cash; } } module.exports = new Bank(); |
Anytime we want to generate a log, just use logger.write()
function. Two implementations of the logger
will be created below.
Implement dependencies
Now we create 2 logger module: console and file:
Create logger-console module
logger-console/index.js
const write = log => console.log(`${new Date()} > ${log}`); module.exports = { write } |
Create logger-file module
logger-file/index.js
const { appendFile } = require('fs'); const { join } = require('path'); const logText = join(__dirname, 'log.txt'); const write = (log = null) => { if (log) { appendFile(logText, `${new Date()} > ${log}\n`, error => { if (error) { return console.log("There was an error writing to the log file!"); } }); } } module.exports = { write } |
Create Client to run
First, we initiate logger
object, we can choose which logger module in need.
Then we use config()
method to inject that dependency.
app.js
const Bank = require('./Bank'); // const logger = require('./logger-console'); const logger = require('./logger-file'); Bank.config({ logger }); Bank.deposit(50000); Bank.withdraw(200); Bank.withdraw(500); Bank.total(); |
You can see that we can change the logger module to use flexibly.
Check result
Run command: node app.js
. Here is the result (in console/text file):
Mon Apr 08 2019 10:32:24 GMT+0700 (SE Asia Standard Time) > deposit: 50000, current cash: 50000 Mon Apr 08 2019 10:32:24 GMT+0700 (SE Asia Standard Time) > withdraw: 200, current cash: 49800 Mon Apr 08 2019 10:32:24 GMT+0700 (SE Asia Standard Time) > withdraw: 500, current cash: 49300 Mon Apr 08 2019 10:32:24 GMT+0700 (SE Asia Standard Time) > check cash: 49300 |