During my tenure as a seasoned, and tenderized, PHP developer I have used many design patterns: adapters, factories, data mappers, facades, etc. The most recent one that I have been working with is Dependency Injection. Inversion of Control is not a new idea, at least not in the programming world, but in the PHP world it seems to have taken us by storm in recent years. Every framework will often have a Dependency Injector built in, or offer it as a component for use. It is the hip thing to do in PHP, but I believe we need to take a step back and evaluate it for what we are really trying to achieve. That is reducing the tight coupling that our objects may have. I view it as removing the new-able’s from our objects code, and handing the object creation over to something else to deal with. Read further on how I believe DI can become an anti-pattern.
Tony Marston, Nat Pryce, Jordan Zimmerman all write about how Dependency Injection is wrong and makes for harder to maintain code. Martin Fowler on the other hand compares service locator’s and dependency injector’s in such a way that they are essentially the same thing, just with differet names. It took me several hours to unwind the conflicts that I had about what these authors had written. For the last several years, like the rest of the PHP community, I’ve read about how Dependency Injection was the best practice. It seemed like it was required in order to be a good developer, promote unit testing, and that service locator’s were bad because they are basically singletons and/or registries.
In a work project I started several years ago we decided to use configuration to control our dependencies, instead of letting the objects instantiate what it needs. It started it’s life as a service locator and after several iterations has become like most, if not all, of the other dependency injector implementations available (minus lazy loading). I’ve come to the conclusion that at the heart of EVERY dependency injector there is a service locator. The DI that Symfony 2, ZF2, Aura.DI, and everyone else uses, are basically using a service locator internally to some degree to locate what it needs. This doesn’t mean you should just switch to using a service locator for everything.
In that same project I spoke of earlier there is currently one service being injected with six (6) services and ten (10) mappers. I’ll admit that the service is doing a lot, it is essentially an aggregate root (the closest terminology I can use for what it does). Not all of the injected objects are used on every request, but since we don’t know what is needed until the request is over it gets them all. Moving those mappers into a very specific Service Locator would reduce the needed injection object to one and keep the dependency instantiation outside of the service. As mentioned earlier I believe that Inversion of Control is meant to remove the object creation and life cycle management outside of the object that is using that dependency.
I believe that both dependency injector’s and service locator’s can co-exist, and both have their usages. Just like any pattern it can become an anti-pattern when overused, and used incorrectly. If you are never going to be injecting a different dependencies why are you using a dependency injector? If you say because it makes unit testing easier, think about it a little harder, I did! Several observations about unit testing the objects we use:
- I often write unit tests without ever using the dependency injection container, I use dependency injection but I inject them manually and directly!
- The service locator can be injected with what you need in order to perform tests too, so therefore the unit under test is still testable.
My belief that dependency injection was going to be the cure all for my work project seems to have backfired. We don’t ever change dependencies dynamically, and have only change dependencies once in the three years of the project. I never use the DI container for testing and my code seems to be a more complex as now I have to manage the dependencies in multiple places.
All that being said, code author’s writing libraries for others to use in their projects, “be kind interface”, to paraphrase the movies name. It makes swapping out the dependencies easier, whether consumers choose to use a dependency injector or a service locator.
I also was beginning to think about this topic and how an overly used di can become difficult to manage. Also, your article was mentioned in this month’s (May 2015) php[architect] magazine, page 61. Well done!
I agree with much of this. One niggling point: though you allude to the distinction, much of the article conflates the strategy of Dependency Injection with the particular case of using an IoC container. Dependency Injection is still a go-to strategy for OOP, but injecting those dependencies doesn’t usually require an IoC container to manage.
I did conflate the two concepts, thanks for pointing that out.
There’s a habit in technology to conflate certain things such as REST and API, when in fact REST is a subset of API. Framework Dependency Injection or Automatic DI is really what you’re referring to.
ADI is a convenience. There are a lot of scenarios where it can easily do things for you and this convenience is especially useful if you’re doing something especially demanding such as unit tests for everything.
ADI becomes an anti-pattern or otherwise problematic in a number of scenarios such as:
1. It is reinforcing another anti-pattern. Certain anti-patterns create huge amounts of additional work. Some of that can be hidden with ADI so you don’t directly observe the cost of what you’re doing.
2. Where you use ADI where it doesn’t work well. Essentially you end up doing things in a really inconvenient way to try to still get convenience out of ADI. There’s a habit for people to use ADI and nothing but ADI which clashes with things such as dynamic code.
3. When you don’t really need ADI. It can complicate things a lot and obscure that’s really going on through unnecessary abstraction. It can also be very hard to debug and significantly complicate your application.
4. Some forms of ADI themselves can lend well to anti-patterns. An ADI is fine when used to give you access to singletons and registries at the top level of your application. There’s nothing really stopping people though passing these down all the way to the leaves of an application in a way that can violate what you might be trying to achieve with ADI.
5. Not all ADIs are the same nor follow the same philosophy. It’s often not clear on how they were meant to be used. More often than not they’re really solving problems the framework had in its way of solving things that were specific to its problems and not necessarily as relevant to your scenario. Others will make assumptions about what you need to do. Some being too complex, some being too simple. Many of the complex ones will give you many ways to do things and this is what people do. They do things in two ways where one is needed.
One of the biggest problems with ADI is that people get coerced into using it when they aren’t really sure how and where it’s probably not really needed. The use of ADI doesn’t start off as “I have this problem, it looks like ADI will really help”. It starts of as either you’re going to have this problem and that’s why you must use ADI off the bat, you get roped into it because it’s a fad, it’s mandatory for the framework in question or from having desires in respect to how flexible things should be that turn out to be unnecessary when truly confronted with the problem. Many people obsess over solutions before problems. Most codebases in the industry as a result have problem free solutions. The reason for this is that many solutions don’t actually depend on their targeted problem. The use of non-problem solving solutions because a problem in itself. You don’t introduce solutions but instead problems. It becomes a problem for example that your real solution to a problem must be compatible with ADI.
It’s common to see people not minimising where you need ADI. A substantial amount of ADI abuse relates to testability abuses. Complex ADI setups tend to be indications of this.
ADI is used almost exclusively for switching between testing and real application mode. That’s an actual scenario where your application might benefit from being able to instantiate two sets of things. Otherwise if something changes that doesn’t need that you mutate the code (an anti-pattern of ADI is using it where you should be changing your code). Even in that case though you have to question the validity of ADI.
What ever happened to instantiating either this connection or that connection based on environment then passing that down to the things that need it? There’s a very worrying trend with ADI of rather than having discrete acyclic trees of instead splaying everything out with inversion to be able to inject into every leaf independently. It makes the leaves instead each a root of their own tree which does wonderful things for your application structure, object graph, keeping things simple, etc. This hints at some conflict. Often the inversion is useful for how people want to do their unit tests but its the opposite of how things want to work when the application is run as a whole.
Although some people see the use for ADI in varying real environments the reality is in those cases it’s often sufficient to replace ADI which will present a configuration artifact for every object with some trivial configuration flags which isolate and express the actual required points of variance for a system. In short, you often see people making everything variant because one in a thousand things are variant. In often cases ADI gets abused as configuration injecting configuration adding a layer of indirection that’s not necessary.
There is a more frustrating style of ADI where people might try to intermix both for reasons that aren’t entirely invalid, however the additional cost of very granular ADI is quite substantial in respect to how you might have to structure your application.
If you stick to the least complex initial case you’ll find there are rarely that many points of injection needed. You really only need injection on IO and if you keep things simple you might for example inject a single test connection to an IO entity rather than inject all of the objects wrapping and abstracting that IO connection. In rare cases you might also need to wrap non-pure libraries that make IO wrapping difficult (but wrapping libraries just so you can tend your code without its dependencies might be an anti-pattern).
Sometimes things get very nasty when someone decides they not only want ADI for their automatic testing but also want to run their application in a test mode with only fake IO wrappers, except their not exactly fake because they do have to support the full functionality state to state of the original IO services.
If you decide that for your relational database you want to use a single fake repository for storing user comments but the real thing for the rest then suddenly things like the ability to use joins go out of the window. I suspect a large amount of the modern movement against relational databases comes from people being required to do bizarre and excessive things with testing, especially unit testing which comes with some serious complexities for dependencies. These don’t just have knock on effects for subsystems and IO but also for ADI.
You’ll find in a lot of cases if you approach how you do testing and configuration differently you can eliminate the need for most of your of ADI.
A great deal of heavy ADI comes from the OOP philosophy of reuse. This is a great philosophy when creating modules for other people to use. I often see code on github insufficiently usable with too much in one class. The problem is with frameworks and ADI is you’re not sharing your code and you can easily change it to be more reusable on demand. What you see with modern ADI combined with OOP is the case where everything is made in a way as to be reusable even before the use cases are known and where well over 90% of your modules wont be reused. You see this maximisation of surface area with an open-graph approach that makes things exponentially more complex.
ADI can help with re-usability. Sometimes people want to use ADI to be able to directly use X without having to instantiate it’s parent composer/factory/container/etc Y. I am not sure where I stand on that. I’ve seen very few scenarios really needing it though and it does make problems when you want lots of things to work together. It’s essentially exposing submodules of a module you might want to keep hidden.
I break my code up into modules that handle certain things. When doing that I’ll think this is how something externally needs to use this module and these are it’s dependencies. It’ll use inversion of control, DI, etc but internally it’ll do things as it needs. It’ll have its own and very own components and it’ll do the injection it needs for those. The outside work does not need to know about that. The rest of the application doesn’t need to know if it doesn’t need to know. The exposure only consists of what needs to be exposed. Modules are also carefully divided as to manage specific concerns, not being too granular or too monolithic. What happens in many OOP approaches will be the notion that those inner workings have to be external independently usable and exposed by default. There’s a notion of classes having encapsulation but so can modules.
What happens when people overuse ADI for this…
First you have to make everything reusable independently. Then when you look at the system you don’t see a true expression of the real structure and interaction points. As in you can’t look at it and see only where things have variance, need to reuse, etc. You don’t see the true pattern of the application. Instead you see all the nodes and all the possible edges unrolled. Then what happens with this is that you have loads of ways to do things. Edges get put in just whereever. You don’t see consistency. It seems nice at first, lets just pluck all the independent components needed on the most atomic fashion. In reality it turns into a mess of things being put done and accessed just anywhere someone feels like in the object graph, behaviour that’s hard to predict (should DI return a singleton or new instance), etc. You can avoid at least some of the worst of this if you keep your ADI top-level.
I’ve never had to use ADI heavily on code I’ve written myself. It’s rarely of benefit but when it is I use it. I use it and everything else sparingly. Only when it introduces value that pays for the additional complexity. Notions that you must use ADI, Unit Testing, Design Patterns, etc universally and by default is where you’re going to see those things becoming anti-patterns.