There are lots of great strategies to keep code manageable and extensible. Today, let's learn about "Dependency Injection".
Dependency Injection
Imagine you're a goldfish named Doug (π ), and you love bubbles. So much so, that you bought a Bubble Machine with a programmable Typescript SDK.
You write a program to make bubbles when you wake up:
import { Bubbler } from 'bubbler';
const initBubbler = () => {
// Instantiate
const bubbler = new Bubbler({ id: "dougs-bubbler" });
// Start the Bubbler
bubbler.bubble({ startTime: "7:00AM", endTime: "8:00AM" })
}
initBubbler();
Great, now you awaken to fresh, well-oxygenated water π¦
You tell your friend Mary (π), and she's so excited, she buys a bubbler too.
You update the code to initialize both bubblers:
import { Bubbler } from 'bubbler';
const initDougsBubbler = () => {
const bubbler = new Bubbler({ id: "dougs-bubbler" });
bubbler.bubble({ startTime: "7:00AM", endTime: "8:00AM" })
}
const initMarysBubbler = () => {
const bubbler = new Bubbler({ id: "marys-bubbler" });
bubbler.bubble({ startTime: "7:00AM", endTime: "8:00AM" })
}
initDougsBubbler();
initMarysBubbler();
It works, but there's something... fishy... going on here...
Instead of duplicating the initBubbler
function, you could have "hoisted" the instantiation step outside the function:
import { Bubbler } from 'bubbler';
const dougsBubbler = new Bubbler({ id: "dougs-bubbler" });
const marysBubbler = new Bubbler({ id: "marys-bubbler" });
const initBubbler = (bubbler) => {
bubbler.bubble({ startTime: "7:00AM", endTime: "8:00AM" })
}
initBubbler(dougsBubbler);
initBubbler(marysBubbler);
Now, we only need the single initBubbler
function, even if your friends Larry (π) and Barry (π‘) decide to buy Bubblers too.
The initBubbler
function is no longer responsible for constructing a bubbler
instance. Instead, it's injected into the function from the outer scope. This pattern is called "Dependency Injection" (DI).
Dependency Injection is a great way to keep your functions simple, modular, and re-usable by delegating responsibility to the caller.
Inversion of Control
Further, because the "caller" is now responsible for initializing the Bubbler (instead of the initBubbler
function), we say control has been "inverted". Dependency Injection is a means by which to achieve "Inversion of Control" (IoC).
IoC Container
The outer scope, responsible for instantiating the bubbler
dependency, is called the "Inversion of Control Container" (IoC Container).
DI Frameworks
You can use a "DI Framework" to make things even easier. Instead of manually initializing the dependencies, a DI Framework acts as the IoC Container and does the work for you.
You just tell the framework which dependencies your function needs, and once they're initialized, the framework automatically invokes your function.
Angular and Nest are two popular tools that include DI Frameworks. Both of these helped in the writing of this article and shaping my own understanding of DI:
- Angular: https://angular.io/guide/providers
- Nest: https://docs.nestjs.com/fundamentals/custom-providers
Plugins
DI Frameworks are great for keeping code organized. However, I like to go one step further and build a module for every "Feature" in my app.
When the DI Framework initializes the "Feature Module", it "installs" itself by invoking dependency methods. It then exports its own API for dependencies to install themselves.
We call call these modules "Plugins", because they inject functionality back into the app.
By building apps as a "Tree of Plugins", feature code is centralized instead of being spread throughout the app.
This makes it easy to mix and match features, build new features, and even open your app for extension by external developers (like Wordpress does).
To learn more about building apps as a tree of Plugins, check out my new package "Halia":
Halia - Extensible TS / JS Dependency Injection Framework
Halia is a simple, lightweight, and extensible DI Framework. It's not tied to a particular backend / frontend technology, and you can customize the framework by installing "Plugins".
Conclusion
We hope your time spent as Doug has helped you see value in the DI Pattern and DI Frameworks.
If you'd like, you can stop imagining you're a goldfish and resume normal human function.
Or, you can imagine youβre a duck and learn how to build Pluggable Apps:
Build Pluggable Apps with Lenny the Duck π¦
All thoughts and comments are greatly appreciated =)
Cheers,
CR
For more articles like this, follow me on: Github, Dev, Twitter, Reddit
Top comments (6)
it seems like i use DI everyday without knowing what is DI actually.
I felt the same when I learned about DI. It's such a simple concept, but it's nice to stake it with a label. Thanks for reading!
This was actually a fun way to see DI, nicely done
Much appreciated Miguel, thanks for taking a look =)
CR- love this, but i think you have a typo in the third code example. You're calling marysBubbler with Doug's bubbler ID. (Extra bubbles for doug!!)
Ah, thank you!! I appreciate the feedback =)