Mark Seemann, author of Dependency Injection in .NET, talks to us about the differences between DI and Service Locators and the importance of having a Composite Root. He also touches on how these all relate back to the SOLID principals of object oriented design.
InfoQ: In your book you surveyed several dependency injection frameworks. Amongst them and others you seen since then, what are the features that make for a DI framework?"
Mark Seemann: In my opinion, the most important attribute of a DI Container is the composition aspect. A DI Container should be able to compose even complex object graphs based on auto-wiring. Contrast this with the (false) meme of 'building a container in 15 lines of code". That has nothing to do with composition, but rather the idea that a DI Container is a sort of dictionary of services, which again leads to the Service Locator anti-pattern. This is why the composition aspect is so important. Fortunately, all DI Containers I've ever seen (apart from Ayende's 15 LoC 'container') support auto-wiring.
Another very important part is lifetime management, because it's very important to make sure that the composed object graph doesn't leak unmanaged resources. Unfortunately, this is a facet of DI which isn't receiving enough attention, so there are huge differences between various DI Containers on how the handle, or don't handle, this issue.
Most DI Containers have many more features than composition and lifetime management, but I consider those the bare essentials.
InfoQ: Can you explain the Service Locator anti-pattern and why it is a problem?
Mark Seemann: The problem with Service Locator is that it hides complexity. This may sound like some people’s definition of encapsulation, but really only means that instead of providing timely feedback at compile time, it pushes discovery of misconfiguration and misalignment of dependencies until run-time. That might still be tolerable if we had no other alternative, but with Dependency Injection we can do everything we can do with a Service Locator, and with fewer drawbacks.
You can say that Service Locator encourages the creation of dishonest APIs because if you just walk up to a class and look at its public API, there’s no way to tell whether it uses Service Locator internally. Such a class may look deceptively simple at first glance, but once you start using it, it’ll throw exceptions left and right if you didn’t configure the Service Locator correctly. In some cases, the Service Locator is configured from an XML file, in which case you might not even discover run-time problems until you’re actually running in production.
I much rather prefer to be told by the compiler that what I’m trying to do is structurally impossible. This is what we can do with Dependency Injection, so it’s just much safer. DI also solves all of the problems that Service Locator claims to solve, so there’s absolutely no reason to ever use a Service Locator.
InfoQ: Likewise, what features so you see as problematic or simply a bad idea?"
Mark Seemann: Once again, my pet peeve is inappropriate lifetime management. The problem is that it's hard to strike a balance. Castle Windsor, for example, tracks all disposable services, but this means that the container itself holds a reference to the services. This means that you must Release any object graph that you Resolve, or you will have resource leaks. On the other hand, this design guarantees that it's not only possible, but easy, to attain deterministic decommissioning of services.
Other containers go the other route where they guarantee that they don't keep references to transient objects. While this seems safer because you may not have a resource leak on your hand, it makes it much more difficult to deal with resource leaks when they do occur.
By default I prefer explicitness to implicitness in API designs, so I find the second approach problematic.
Other features I'm not too fond of is an exaggerated focus on XML or attributes. Fortunately, these types of APIs are on the way out.
InfoQ: Have you seen any notable improvements in DI frameworks since your book was written?"
Mark Seemann: Not really. Since I started writing the book I've seen improvements, but they've been more of the incremental nature.
One of the most important developments during the last couple of years has been the rise of convention-based registration. The whole ideas is that we want to push the container towards being a pure infrastructure component that automatically figures out how to compose the correct object graph based on conventions (like names and which classes implement which interfaces). Some containers have moved farther in that direction than others, but I definitely think that this is the way containers have to move to stay relevant.
Some containers - again, most notably Castle Windsor - are also moving towards a more ingrained understanding of compositional design patterns such as Decorator and Composite. When combined with convention-based registration it opens for some very powerful constructs.
Another interesting development goes in a completely different direction. The Hiro container attempts to address the performance overhead that some people seem to be very afraid of. It does that by on the fly auto-generating and compiling the container into IL instructions. This makes it much faster than traditional containers. That's very interesting, but I still think that it attempts to address a problem that doesn't really exist in the first place.
Apart from that I don't really see a lot of development potential for DI Containers. They seem pretty mature to me, and while there are still more finishing we can apply to them, I can't really see something new and radical on the horizon... but then, making such a 'prediction' seems to be a pretty sure way to make a fool of oneself...
InfoQ: Have we reached the limits of DI technology or are there any improvements that still need to be made?"
Mark Seemann: I must admit that I find viewing DI as a technology a bit irrelevant. As I previously said, DI Containers are already pretty mature. What's really lacking, at least in the .NET world, is an understanding that DI is really about principles and patterns, and not about technology.
As .NET developers we have been raised by Microsoft to believe that every problem can be addressed with a tool. When it comes to loose coupling, DI Containers look like enabling technology, but it's really, really important to realize that the solution doesn't lie with the tool. It lies in the understanding that DI (or Inversion of Control) is a fundamentally different way to reason about and design software.
This relates very closely to the SOLID principles - in fact, DI is just an elaboration of the 'D' in SOLID, so it's far from new knowledge. In my opinion, SOLID very nicely captures the essence of OOP, and if you write SOLID code, you also utilize DI. Technology in the shape of a container is totally optional at this point.
Although .NET at its heart is an object-oriented platform, most developers still write procedural code. There are lots of potential improvements, but the most important ones are to raise awareness of proper OOD.
InfoQ: Some people are dead set against any form of dependency injection they don't roll themselves. Others obsessively use DI frameworks even when it is clear they aren't appropriate. Where is the balance point between the two extremes?"
Mark Seemann: That's actually pretty easy to answer, because once you realize that a DI Container should be treated as a purely optional infrastructure component that only addresses composition of entire object graphs, it becomes very easy to isolate them into a small part of the application. I call this the Composition Root.
The beauty of it is that once you've isolated composition to the Composition Root the decision about DI Container usage becomes unimportant. You can decide to start out with Poor Man's DI, then a couple of weeks later decide to try out AutoFac. A month later, you may decide to migrate the Composition Root to Castle Windsor, or perhaps even go back to Poor Man's DI. When composition is isolated in the Composition Root, making that kind of change isn't particularly hard.
Although DI should be pervasive in application code, any reference to a specific DI Container should be totally absent.
InfoQ: Would you mind going into a more detail about what a composition root is and when you should use one?
Mark Seemann: A Composition Root is an isolated part of an application’s entry point where we compose the entire object graph for the application. For long-running applications such as desktop applications or Windows services or batch jobs, this typically means composing the entire object graph in a single statement. This basically happens in the entry point of the application. You basically create the graph supporting the application and hand off control to it by calling Execute or Show or something similar. At that point, the composition is over and the application code takes over.
Something similar happens with request-based applications such as web sites or services. However, in this case we compose an object graph per request, but the idea is the same: compose the graph and hand off control to the graph, at which point composition is over.
If you decide to use a DI Container to support composition, the container implements the Composition Root, but must never be allowed to leak outside the Composition Root. However, it’s also perfectly valid to implement the Composition Root with Poor Man’s DI.
Together with Constructor Injection, Composition Root is the core DI pattern. If you apply DI, you should always have a Composition Root.
InfoQ: I know it is hard to speak in absolutes, but are there any circumstances where you think DI must be used? Or should never be used?”
Mark Seemann: DI is really just a way to enable loose coupling. I’m only aware of two ways to enable loose coupling: DI and Service Locator, and since Service Locator has disadvantages that DI doesn’t have it follows that DI is the only appropriate way to apply loose coupling.
Now the question becomes: when should we care about loose coupling? My answer is that it’s almost always relevant. Loose coupling is a way to make application code extensible. As developers, we spend most of our time extending existing software; every time we add a feature to existing software, we extend it. We can make this hard on ourselves with tightly coupled code, or we can make it easy with loosely coupled code.
There are a few cases where this doesn’t matter. For example, I’ve heard about game producers where it was pretty much a given that if they were unable to release a new title in time for holiday sales, the company would not remain in business. In such a case maintainability is secondary to other concerns, so loose coupling may be irrelevant. However, in the vast majority of cases I must insist that DI is very relevant.
InfoQ: Some say a major benefit of DI is that it allows for changing implementations in a deployed application just by using configuration files. Others say that is a really bad idea because it subverts the testing process. Where do you stand on that debate?
Mark Seemann: I hear the ’late binding’ argument a lot, and it’s true that you can use DI to implement late binding. However, it’s just a very small subset of what you can do with DI, so people who believe that this is the only purpose of DI are missing out on all the other fantastic things they could do with loose coupling.
On the other hand, I’ve never heard it claimed that it subverts the testing process. Quite the contrary I’d say that unit testing a code base is virtually impossible without DI.
What I find quite interesting is that while DI is still regarded as somewhat controversial in places, most development organizations seem to have agreed that they’d like to write SOLID code. Ironically, though, the ‘D’ in SOLID stands for the Dependency Inversion Principle, which is the guiding principle behind DI – so if you accept SOLID, then DI follows.
About the Book Author
Mark Seemann is the creator of AutoFixture and the author of Dependency Injection in .NET. He is a professional software developer and architect living in Copenhagen, Denmark, and currently a Software Architect for Commentor, a Danish consulting company. He enjoys reading, drawing, playing the guitar, good wine, and gourmet food.