What Dependency Injection Is? And Why?
2020-10-04Background
Dependency injection is a powerful design pattern that used in various popular frameworks and libraries. Spring framework in JVM eco-system, for example, is one of the most popular frameworks, that built heavily around dependency injection.
In this article, inversion of control, dependency injection, and other related concepts will be explained, with their beneifts and drawbacks, by a coding situation.
Situation
You are creating a component which would do some logging to help user troubleshoot. When planning this feature, you are thinking how to handle logs generated.
There are few dozens ways to handle logs including print to console, append to files, pipe to elastic search, etc. Implement all of them is not feasible.
Solution
Inversion of Control
Since you don't do it, you allow the component user to write their own way to process log. This is the concept of inversion of control. In other words, you give the control over logging to the consumer.
For this to work, you need to add a new layer of abstraction for consumer's code to response to logging, while this implies extra codes, but this is much better than catching up all the logging possibilities out there.
Inheritance
You make your class abstract and force user to override the logging functions. This utilizes template pattern, that is, you define specific steps, i.e. one logging step, and it is up to user to decide how to implement it.
However, inheritance is restrictive. For example,
- if you allow other aspects of your component, say database access, for users to customize, either they mix all those code in one inheritance, or, extend multiple times for each aspects resulting deep inheritance hierarchy.
- if consumers need to do logging in different components, they will have to copy the same logic to those components.
Composite over Inheritance is a principle in object oriented programming addressing the inflexibility caused by inheritance, but this is out of the scope of this article.
Dependency Injection
To avoid limitations caused by inheritance, you decide to take logging out to another abstract class and your component will then depend on the concrete implementation provided by user. This pattern is called dependency injection, that you allow user to inject dependency you required by their own version. In this way, each objects are focused in single responsibility, so the code is more manageable, and reusable.
As the code base grows, more dependencies are introduced, and these relationships become more complex. Although libraries and frameworks, such as Spring for JVM, can help to manage these dependency graphs, sometimes graph problems, e.g. circular dependency, still need to be handled by hand.
Event bus
Another way is to use an event bus, so your component will post a log event to a bus, and the listener, again, implemented by user, will react to that event, like append to log file.
Using event bus, logging and your component are decoupled, and they only need to depend on the event and the specific logging event. Also, adding extra log handling is relatively easy.
However, this comes with some drawbacks.
- Adding extra dependence, the event bus and usually extra event type for logging.
- Debugging made harder as debug tools and IDEs could not easily relate log handling and the point where you invoke logging.
- Introducing single point of failure problem since event bus is usually the only one in the whole apps.
- Stalling messages, depends on bus implementation, many message handlers or bad handlers may cause message stall at some point and thus making whole app not responsive.
Closing
Here, the concepts, pros and cons of inversion of control, dependency injection and event bus are covered. I hope this will help you can understand them, and pick proper patterns to write more good code. Happy coding 🙂
If you like this article, remember to show your support by buy me a book.