Hosting a Biryani Party: A Tale of Dependency Injection and Inversion of Control
In the world of software development, managing dependencies is a critical aspect of creating maintainable, scalable, and testable applications. If you are familiar with Object Oriented Programming (OOP), then you have probably heard about the S.O.L.I.D principle.
These are five software development principles for building scalable and maintainable software, made popular by Robert C. Martin. In this article, we are going to talk about the last of these five principles, the D (yes, pun intended). D stands for Dependency Inversion.
Q: What does this mean?
A: Good question. The principle suggests a high-level module should not depend on low-level modules, both should depend on an abstraction. Also, abstractions should not depend on details, it should be the other way around.
Q: English, please.
A: OK OK.
Let’s break it down with a simple and tasty example. This is the plot, you're hosting a grand celebration, and the highlight of the event is the delicious biryani. In simple terms, it's like saying your biryani party planning shouldn't depend on the specifics of the biryani itself. Instead, both the party planning and biryani details should rely on an abstraction for our case a catering service.
Let’s start with the scenario where we directly handle the biriyani details inside the party organizing component.
import { useState, useEffect } from 'react';
export default function BiryaniParty() {
const [biryaniDetails, setBiryaniDetails] = useState({});
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('/api/biryaniDetails');
const data = await response.json();
setBiryaniDetails(data);
} catch (error) {
console.error('Error fetching biryani details:', error);
}
};
fetchData();
}, []);
return (
<div>
<h1>Biryani Party Details</h1>
<p>Customized Biryani: {biryaniDetails.ingredients}</p>
</div>
);
};
What are the complications?
Code Rigidity: The
BiriyaniParty
component is tightly coupled with the Biriyani details fetching logic. Changing the data source or reusing the component with different data is challenging. Also, does it matter where I get the biryani from? I can throw a party if I get the biryani from place A, I can throw the same party if I get it from place B. It doesn't matter where I get the biryani from, because my end goal is, throwing the biryani party, right?Testability: Testing is a bit complicated since the biriyani details fetching is happening directly inside the component and we may need an actual API for testing.
Code Duplication: If similar logic is needed elsewhere in the app, we need to duplicate the whole component, making it harder to maintain.
Limited Reusability: The component is less reusable since it is bound to a specific biriyani details fetching mechanism.
How to solve this?
Picture this: you hire a biryani catering service, injecting flexibility into your party planning. Now, instead of handling biryani details directly, you pass a 'biryaniService' as a prop to your 'BiryaniParty' component. It's like hiring a pro biryani chef for your event!
import { useState, useEffect } from 'react';
export default function BiryaniParty ({ biryaniService }) {
const [biryaniDetails, setBiryaniDetails] = useState({});
useEffect(() => {
const fetchData = async () => {
try {
const data = await biryaniService.fetchBiryaniDetails();
setBiryaniDetails(data);
} catch (error) {
console.error('Error fetching biryani details:', error);
}
};
fetchData();
}, [biryaniService]);
return (
<div>
<h1>Biryani Party Details</h1>
<p>Customized Biryani: {biryaniDetails.ingredients}</p>
</div>
);
};
// BiryaniService.js
const BiryaniService = {
fetchBiryaniDetails: async () => {
try {
const response = await fetch('/api/catering/biryaniDetails');
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching biryani details:', error);
throw error;
}
},
};
export default BiryaniService;
Now, in your main application component, let's call it 'PartyPlanner,' you use the 'BiryaniParty' component and inject the 'BiryaniService' as a prop. It's like choosing the biryani chef for your grand party!
// PartyPlanner.js
import BiryaniParty from './BiryaniParty';
import BiryaniService from './BiryaniService';
export default PartyPlanner () {
return (
<div>
<h1>Grand Biryani Party</h1>
<BiryaniParty biryaniService={BiryaniService} />
</div>
);
};
Benefits of Dependency Injection and Inversion of Control
Code Flexibility: Changing biryani service providers is now seamless. We can easily switch between the actual service if we want.
Improved Testability: Testing becomes more straightforward as we can provide a mock biryani service during testing, ensuring that our tests focus on the component's logic without involving actual API calls.
Reduced Code Duplication: The biryani fetching logic is centralized in the injected service, reducing redundancy. If multiple components need similar biryani data, they can all use the same service.
Enhanced Reusability: Our BiryaniParty becomes more reusable since it's decoupled from the specifics of biryani details fetching. We can use it in various contexts by providing different biryani services.
Conclusion
Hosting a biryani party becomes a breeze when you apply Dependency Injection and Inversion of Control, right? By hiring a catering service and injecting it into our party planning component, we achieve a more flexible, testable, and maintainable solution. Happy biryani party planning!
Thank you very much for reading. I hope you had fun reading about the Dependency Injection and Inversion of Dependency with this tasty and fun analogy as did I writing this.
If you have any suggestions or questions, please leave a comment. I am going out for biryani.
\Cover image is "Designed by Freepik"*
Subscribe to my newsletter
Read articles from Md Mizanur Rahman directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by