Dependency Injection, A Brief Monograph
Or, How To Survive Phil
Programming School Student: “Would this be considered dependency injection?”
It’s hard not to have an answer to a question, especially when I’m supposed to be the “expert”. The student was specifically referring to passing a params hash to a Ruby method. I allowed that it is kind of dependency injection, but that there’s much more to the topic. After some discussion, it soon became clear that, while the student had heard of dependency injection, the pattern’s use wasn’t very clear.
This is an exploration of the power of dependency injection. And a story about Phil.
Monday morning. Our lead product manager — let’s call him Phil — walks into the open office space and announces the new “Profitability Initiative 1.0” — we must integrate the Super Colliding Aptness Matrix immediately. It employs massively-neural quantum machine learning. Rocket Scientist stuff, don’t worry about the details.
Phil has the specs and API documentation from SCAM, Inc. in hand. Our job, connect the company’s Ruby application to this new system and introduce profitability into our company.
So, we start writing code…and by Tuesday, we have it done.
The ProfitCenter class knows an awful lot about the SCAMService: the name of the service on line 10 and the specific ordering of parameters the profitate method takes on line 14.
The SCAMService is just a wrapper we created to hide the mysterious complexity of the SCAM API, but now we have exposed some of that complexity to the class that uses the service.
Add the fact that testing is complicated: we have to mock the SCAMService and probably stub the profitate method to keep our tests fast.
It’s not perfect, but the sooner we get to production the sooner we profit. And SCAM, Inc. seems like it’s here to stay. Let’s ship it!
Wednesday morning. Phil is back. SCAM, Inc. is out! Apparently their technology was really based on carnival chickens playing tic-tac-toe and random atomic decay. We’re switching to “Profitability Initiative 2.0, The Profitening” — and we have a new partner: TRUST LLC. Their product, the Terribly Repugnant Unpleasantly Slippery Triax — it’s apparently some kind of biological, trans-dimensional entity. Real Brain Surgeon stuff. Sounds cool anyway, and Phil hands over some really stellar API documentation.
[T]he open/closed principle states “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”; that is, such an entity can allow its behaviour to be extended without modifying its source code. — Wikipedia
We have a coupling problem. The ProfitCenter class knows all about the obsolete SCAM, Inc. product. Now we have to rip all that out and replace it with the TRUST, LLC product.
So we code…and by Wednesday night we’ve finished.
We had to create the TRUST service object and then bust open the ProfitCenter to remove SCAM and add TRUST. Also, we had to write new mocks and stubs for the TRUST tests.
Seems like we may have violated the open/closed SOLID principle. We had to open up the ProfitCenter and muck about its internals to integrate TRUST. Yep, that’s a violation.
If we know one thing about Phil, the guy loves to change things. And being good agilists, we embrace change. So how can we make our code more open to extension but closed to modification?
Let’s try dependency injection for the new TRUST integration. So we code…and by Thursday morning we have a solution.
The ProfitCenter initializer now takes the service instance as a parameter. It no longer cares what service we’re integrating. So long as it conforms to the business requirement that it takes a margin target and a risk factor.
Now the ProfitCenter is completely extensible. We can even have different instances of our ProfitCenter that use different services. Perhaps Phil would like to have a “bake off” between SCAM and TRUST, and now we can easily do that. It’s possible the carnival chickens are better than the Triax.
We can also simplify our tests. We no longer need to be concerned about how to mock and stub the service we’re integrating. We can create a simple struct or class that takes in the parameters we expect, implements just the method calls we need, and returns known values.
Friday morning. Phil strides into the open office space to announce “Profitability Initiative 3.0: The Last Crusade” and this time we’re ready for him….
Dependency injection lets objects work together without sharing deep knowledge: code is decoupled, APIs are narrow, and testing is simpler.
For more on the awesome power of dependency injection and other object oriented design patterns, check out Practical Object Oriented Design in Ruby (POODR) by Sandi Metz. While Sandi uses Ruby in the book, it will be useful to anyone working in a dynamic OO language.
Many thanks to Justin Abrahms and Chandra Carney for their comments, corrections, and suggestions. And thanks to the Turing School for the opportunity to work with such great students.