Another pile-on story; this time on David Heinemeier Hansson’s Dependency injection is not a virtue. I agree with every word DHH writes here, but I think I have a better example. Tl;dr:

  1. Statically-typed languages can make unit testing hard, so

  2. People adopt dependency injection to work around this, and

  3. In a sort of Stockholm-syndrome effect, people argue that DI is A Good Thing and over-use it, to harmful effect.

Another Example · DHH’s example is slick, but the publish! method includes enough deep-Ruby idioms that I bet it’s opaque to a lot of perfectly smart developers who think in Java or C# or ObjC or whatever.

Let’s do a more meat-&-potatoes example: Unit testing a basic HTTP call. The context is my dinky little google-id-token gem (where “gem” is Ruby for a standard library package). To validate a Google ID Token, you have to fetch Google’s OAuth 2 public keys and check the digital signature.

So your unit tests obviously have to check the cases where the key retrieval works and doesn’t work. If you were a Java programmer you’d probably type something like mock httpurlconnection or unit test httpurlconnection into Google. Frankly, the answers are not that great, and probably DI is in your future.

I’m not going to display the Ruby code from my refresh_certs method, it’s a straightforward call to the Net::HTTP library.

So I typed ruby unit test net::http into Google, and there were a lot of different options; a variety of general-purpose mocking tools (that don’t require you to change your app source), but for some reason I decided to use fakeweb (which doesn’t require you to change your app source).

Here’s a chunk of my test code:

it 'should complain if unable to fetch Google tokens' do
  FakeWeb::register_uri(:get, CERTS_URI,
                        :status => ["404", "Not found"],
                        :body => 'Ouch!')
  t = GoogleIDToken::Validator.new
  t.check('whatever', 'whatever').should == nil
  t.problem.should =~ /Unable to retrieve.*keys/
end

it 'should successfully validate a good token against good certs' do
  FakeWeb::register_uri(:get, CERTS_URI,
                        :status => ["200", "Success"],
                        :body => @certs_body)
  jwt = @validator.check(@good_token, @token_aud, @token_cid)
  jwt.should_not == nil
  jwt['aud'].should == @token_aud
  jwt['cid'].should == @token_cid
end

The Dependency-Injection Big Picture · DI, like Wikipedia says, “allows removing hard-coded dependencies and making it possible to change them, whether at run-time or compile-time”. Surely that has to be a good thing, right? Since the solution to every problem in Computer Science is another level of indirection, right?

Obviously, if you have to have DI to have unit testing, then you have to have DI. But in practice, when I try to read application code based on DI frameworks like Guice or Spring, it feels like there’s a whole lot of stuff between me and the business logic, and I have to understand it all to understand anything.

When a veteran Java-head or Rubyist sees calls into well-known standard libraries, they instantly know a whole lot about what’s going on; obfuscating them with factories and implementors and injectors and so on significantly impairs readability, and that’s a big deal. One of the nicest things about Java is its huge repertoire of well-known well-documented battle-tested APIs for more or less anything; they add significantly not just to its capabilities, but its expressiveness and (especially) readability.

Also, call me old-fashioned, but I think that, as far as statically-typed languages go, Java’s “interface” mechanism offers just the right level of indirection, semantically: I don’t care what it is, I care what it does. Too bad you have to pile DI on top of that.

This is another reason why dynamically-typed languages are usually a better choice for implementing application programs.



Contributions

Comment feed for ongoing:Comments feed

From: Jeffrey Yasskin (Jan 06 2013, at 16:22)

This doesn't really speak to your claim about dynamic languages, but I don't think your post is really about DI. It may be about DI frameworks instead. You praise Java interfaces, which are the mechanism DI itself uses to replace implementations with mocks, but there's a question of how you replace a production implementation with a test one. That's what DI's factories and the frameworks are for.

fakeweb seems to use another technique: If there's a global variable that all calls go through, you can inject dependencies by modifying that global. In dynamic languages that global variable can be an imported module itself, if the testing library hooks in early enough. (This is also possible in mostly-static languages with LD_PRELOAD or classpath tricks.) It's still DI, just accomplished in a different way.

[link]

From: Ben Hardy (Jan 06 2013, at 16:34)

You may find the Cake Pattern from Scala interesting. Neat separation of concerns. Low on ceremony. Easy testing. http://jonasboner.com/2008/10/06/real-world-scala-dependency-injection-di/

Aside from that, the assertion that dynamically typed languages are inherently better for building applications is... controversial. Here's why: without static types, your code is only as good as your tests.

With a real type system you can actually lean on the compiler to do lots of correctness enforcement for you, by designing your types so that incorrect usage is impossible (or at least very difficult).

I get that the Rails crowd has a very convincingly low-ceremony system, and that this can be quite compelling. But bear in mind that there are statically typed web frameworks that are equally low on ceremony but with the delicious bonus that a lot of correctness verification is done at compile time. Check out Scalatra, for example.

Hell, even Spring 3.1 with pure programmatic config and autowiring isn't bad at all, and this is a very capable and mature framework. I don't see how anyone would find this kind of DI painful, especially if using a nice concise language like Scala.

[link]

From: Anonymous coward (Jan 06 2013, at 17:15)

With dependency injection, you have to build that into your production code, even if there is only one implementation. Same with interfaces. How many times have I been in an IDE and said "go to definition" and found an interface and said "find all implementations", and there is only one and I'm all "grrr...". Or worse, have to go looking for a dependency file to figure out what is actually being plugged in. For all the positives of strong typing, that isn't one of them.

All that being said, I've never tried working in a million line dynamically typed code base.

[link]

From: Rafe (Jan 06 2013, at 17:30)

I wrote one large DI application in my career (using Spring), and it wasn't for testing. I didn't really write unit tests at that time. It was to keep from writing code aside from business logic, and it worked very well. Things got better as the application evolved thanks to annotations and Spring's cleverer handling of configuration, but even at the beginning, most of the code I wrote was not factories and whatnot but pure business logic. The gunk was all part of Spring, and my code was pretty lean thanks to dependency injection. If I were going back to rewrite an application like that today, there are a lot of things I'd do differently, but overall I'm certain that DI was a net benefit to that project.

[link]

From: Cedric (Jan 06 2013, at 17:58)

I completely fail to see the connection between dependency injection and whether your language is statically or dynamically typed. You can do, or not do, DI in both, the presence of type annotations is completely orthogonal to that concern.

Somehow, that point seems to be completely lost on DHH as well.

[link]

From: David (Jan 07 2013, at 00:54)

tl;dr:

- DI isn't about unit testing. It's broader than that.

- It's about a 3-line refactor to make it easily testable in Java - and a good refactor at that.

I view things slightly differently:

- People building large applications want flexibility

- People building large applications want a mechanism to help describe and explain the structure and complexity of their application

- People building large applications want separate teams to be able to work on components of the application independently, and to compose reusable components in different ways.

In order to satisfy these requirements, they turn to a new abstraction layer. In Java, this is where DI gets involved.

DI:

- helps clarify component boundaries

- provides an easy way to use different implementations of an interface (sometimes multiple at once)

- provides a point of abstraction where to easily (and cleanly) wrap or intercept calls

- it's also useful for unit testing

The 'cleanly' point is important. I could use classloader magic and bytecode writing libraries to implement all manner of interesting mocking libraries to aid unit testing. That might be fine for my tests (and I might not be able to do HttpUrlConnection because it's in the java.* namespace with a horrible design - nicely chosen), but I wouldn't want to do that for production code for a couple of reasons:

1. It's risk prone, complex, and difficult to maintain

2. It makes it much harder to understand what the application is doing as there's no obvious point of injection of the magic functionality, and probably a lack of clarity over what magic is created by what team on what class

I'd argue that point 2 would apply to similar magic in dynamic languages, even if point 1 doesn't.

If you want to unit test a method using HttpUrlConnection, use something like mockwebserver [1] and simply have the method take a URL. That's a non-Guice, non-Spring, low overhead form of DI that is common across languages and has the benefit of making your code more useful and clearer to readers about what the method will do (regardless of the language)

[1] http://code.google.com/p/mockwebserver/

[link]

From: sf@fermigier.com (Jan 07 2013, at 02:18)

Alex Martelli wrote and gave several talks about DI in Python over the year. Cf. for instance:

http://www.aleax.it/yt_pydi.pdf

Given that Alex is one of the most experienced Python developers and most respected Python educators, specially in the context of enterprise applications, I tend to give some credit to his approach (which, he says, is widely used at Google).

S.

[link]

From: Chao (Jan 07 2013, at 05:58)

I am seeing a lot of concepts in your article such as YAGNI, DI, interfaces, testable code etc... It seems that you think if we want to write testable code in Java, we have to break YAGNI and write a bunch of stuff that we don't need, such as DI...

I agree that in Java we tend to write more code (though the IDE generates a lot of it), but that's not because we are using DI.

I am sure you know that DI is simply: "ask for your dependencies, don't create them". which is generally a good practice for decoupling your code, and it doesn't necessarily result in more code:

public void publishNow() {

this.save(new Date());

}

blog.publishNow();

vs

public void publish(Date d) {

this.save(d);

}

blog.publish(new Date());

Also, about factories, rarely should you inject factories into your methods, if your method requires an instance of a class, just ask for that class, don't ask for a factory and create the instance inside the method, this is breaking DI in the first place.

[link]

From: Damian (Jan 07 2013, at 06:09)

This seems to be more concerned with functional versus side-effect ridden code and testing. As others have said, static typing is a red herring and DI is only peripherally involved.

I'm much more confident testing code like lookup_name(id, mock_http_getter) than HTTPClient.set_default(mock_http_getter); lookup_name(id) because the isolation is apparent.

Refinements in ruby might help isolate the test environment but, as people like headius have pointed out, they may be more trouble. DI is a way to provide that environment.

[link]

From: Johnny (Jan 07 2013, at 06:40)

I wrote Java for years, dabbled in Ruby, then evolved into a full time Rubyist. There's been a lot of backlash against DI stuff in Java; and for good reason. But I think that it provides some nice standardization; I don't have to go hunt down for wherever you are bootstrapping your database connection pool, etc. I had to manage some Java developers, and to be honest the level of sophistication there is less. This matters way less at shops that are hiring top tier talent, but in the world where your manager is more interested in the quantity of your underlings than the quality (I quit that job, by the way) you need to provide a clear pattern, otherwise, they will go write all kinds of code that is tangential. You will find the time wasted on a bug in com.company.DbUtils - It's easier to say "You will use the pattern outlined here with Spring to bootstrap these sorts of things in your application". It becomes a necessary evil when you have a number of developers collaborating on a larger Java project. Of course, if you're a good Java shop, you can have your standard libraries that handle this and everyone is working on small, loosely coupled things to begin with rather than refactoring some facet of a monolithic turd.

That said, I am grateful that I haven't had to think of these things in a while. I don't miss DI at all in Ruby land.

[link]

From: John Cheng (Jan 07 2013, at 09:25)

Aside from testing, one of the goals of dependency injection is to separate the definition of dependencies from business logic. Too often much of our code is importing other modules and configuring them with the right parameters, or perhaps importing and aliasing a different module if unavailable (e.g., falling back to a custom SSL library if openssl is not installed on the system), or some other logic just to setup and validate preconditions. In other words, business logic gets lost behind a series of "configuration" code. In addition to helping with testing, DI also helps keep configuration code out of business logic.

This is probably less of a problem in Ruby than Java because Ruby is more concise. However, I imagine there are times when your code have stuff like this duplicated in 4 or 5 places

foo = Foo(parameter)

bar = bar::BAR::Bar(foo)

baz = bar::BAR::baz(bar)

In which case it would be nice to have a central place where the dependencies of baz is defined and have a pre-configured instance of baz provided to you. That's one virtue of dependency injection.

[link]

From: Dan (Jan 23 2013, at 10:23)

This is all very nice, but can you run your tests in parallel?

If not, then I don't think you've solved the need for DI just yet. Regardless of language.

[link]

author · Dad
colophon · rights
picture of the day
January 06, 2013
· Technology (90 fragments)
· · Coding (98 more)

By .

The opinions expressed here
are my own, and no other party
necessarily agrees with them.

A full disclosure of my
professional interests is
on the author page.

I’m on Mastodon!