➠ Website of David Bryant Copeland

What Dependency Injection Really Is

Jan 7th, 2013

Both DHH and Tim Bray make great points about “dependency injection” and its issues regarding Ruby and
testing. My colleague Adam Keys makes a similar point, though doesn’t call his anti-pattern “dependency injection”.

The scare quotes are because neither DHH nor Tim are accurately representing the purpose of dependency injection. Dependency
Injection is not about a framework, XML, factories, or testing. It’s about simplifying code. Let’s see how.

I’m going to ignore Rails for the moment, and just talk about designing classes in Ruby1. Let’s take the example
domain from my previous post, and expand it a bit. We start with a class that implements purchasing logic:

I’ve simplified things a bit, but basically we charge the customer, create an invoice, and add it to their invoices. This class
gets used as part of the regular purchase flow of our website. Suppose that we want to run a promotion such that, whenever
someone signs up for our mailing list, they get a free keychain. They’ll get this keychain the same as if they bought it, but
it’s a special product with a price of zero, so they get it for free.

This is straightforward, simple code. We add the customer to the mailing list, find the promotional keychain, and “purchase” it.

What are the dependencies of this class?

The MailingList object

The Product object

The PurchaseProcess object

An instance of the PurchaseProcess class

Of those four dependencies, only three are directly related to the business process of our promotion. The PurchaseProcess
object is only needed to create an instance of that class so we can call purchase_product on it. Let’s inject this dependency
and see what happens.

Our class is a bit longer, but it has fewer dependencies. It also does fewer, things, making it more cohesive. It’s only about this promotion, and no longer about creating instances of a shared class. If the way in which PurchaseProcess instances get created needs to change, we will not have to change this class, meaning its fan out is lower. It is simpler, by any objective definition.

Other than simplicity, what are some advantages of this approach?

We can use an alternate purchase process if we want. Swapping purchase processes is certainly YAGNI, but it’s not hurting anything and it’s a nice benefit.

Flexibility in testing. Since we no longer depend on the PurchaseProcess object, we have more options regarding how we test KeychainPromotion. Before, our only option was to mock/stub new, but now we can just pass in anything that responds to purchase_product. Our test will be simpler because of this.

Note that these are side benefits, not ends unto themselves. Code that’s easier to test isn’t better because of that fact -
it’s the other way around. Code that’s well designed is easier to test. We have some objective measures of the
quality of code, and many of them lead to simpler testing.

This is the mistake that both DHH and Tim make in their posts - they assume that the reason for using dependency injection is to
make your code “easier” to test. In DHH’s (and Adam’s) case, they rightly call out method-level injection as bad. I would agree,
and, outside of the mind-bending Scala collections framework, you don’t see it much. Tim asserts that DI is nothing but needless indirection, but that’s not it at all.

Suppose we want to inject more dependencies into our KeychainPromotion class. Let’s replace the “hard-coded” dependency on
the Product object with an injected finder and see what happens.

Is this better? We haven’t reduced the number of dependencies on this class, even though we increased the complexity of the
constructor. The sign_up_for_mailing_list method is also more complex because it now depends on a new ivar, @product_finder,
which has a higher scope than a direct use of Product. While it’s true that KeychainPromotion is more flexible and we have more flexibility in testing it, we’ve made the code itself more complex.

I would argue that injecting this particular dependency is not an improvement, and that this code is worse than before.

Is that the fault of the concept of Dependency Injection? Of course not. Dependency Injection, like any other pattern, technique, or tool, is only useful when it’s used properly. It’s true that dynamic languages like Ruby provide many other tools that solve problems that Dependency Injection can also solve, and they often do it better. But it doesn’t mean that the entire
concept is worthless.

Rails is a container, and it manages our code for us, the same as Spring or Guice. As such, coding “The Rails Way” has significant advantages that can outweigh those that we might see by using Dependency Injection ↩

About Me

I'm a programmer, musician, and author. I speak frequently at national and regional conferences and spend my days currently as a lead engineer at Stitch Fix. I've written applications in C, C++, Perl, Ruby, Scala, and many others, on large and small teams. As a developer, I believe in clean code, making it right, providing a great user experience and using the right tool for the job. As a bass player, I believe in using a pick, locking it down, and ripping off Peter Hook. As an author, I insist on the Oxford Comma, try to avoid semi-colons, and dislike title case.