DI, DiC, & Service Locator Redux

October 10th, 2012 § 17 comments

To DiC, or not to DiC: that has seemed to be the question in PHP for the last few years. Most people generally agree that injecting dependencies is the right thing to do®. For those writing a framework, or any shared codebase where extensibility or the ability to grow the codebase is a core philosophical tenet, not injecting dependencies is doing a disservice to the project in the long run. So, as I’ve stated before, the question becomes how do we manage the added complexity that comes with practicing dependency injection?

Recapping What a DiC Is

A DiC (dependency injection container) is a special type of object container that acts as one part object registry, one part object factory. In most common and popular implementations, the container is seeded with or has access to data about what the code looks like (the class names, methods parameters), and also the runtime information (the parameters, like usernames, and application specific information.) Sometimes there is a clear division between the two, sometimes they are blended together. Either way, we can assert that to be a DiC, a container must do at least one of:

  • Auto-instantiation: the ability to act as a factory, by creating objects. This means that the container is responsible for either calling new or calling a method that will eventually call new to create an object.
  • Auto-wiring: the ability to introduce one object as a dependency to another. This means if object A is required to successfully produce a valid object B, the container is capable of finding or producing object A before introducing it to object B. This could be through constructor injection or via setter injection.

In my book, failing to achieve either of those disqualifies a container from being a DiC.

What A Service Locator Is

A Service Locator, on the other hand, is better defined by a usage pattern, rather than the signature (pattern) of any particular class. This means that any object, upon being injected into a consuming object and then asked for an object, can be a service locator if it is capable of producing the requested object by a particular name or type. Effectively, a service locator can be a special container, or not; it can be a registry, or not; or it can be a factory, but perhaps not.

The only stipulation is that you’ve provided an object (the service locator) as a dependency to another object so that that the consuming object can then use the provided object (the service locator) to locate any dependencies. Instead of injecting n dependencies, you inject just one: the service locator. In code, it looks like this:

The constructor for this ThingThatHasDependencies, might look like this:

Now, you can see that any instance of ThingThatHasDependencies is now Container aware, which, depending on the context of the ThingThatHasDependencies, might be a good (or a bad) thing.

When to Use Which, In PHP

The decision of whether or not to use a Service Locator approach or use a DiC should be asked and answered not for your code base as a whole, but for each layer or component. The Service Locator pattern should be applied only in places where the it makes the most sense. There are a few simple questions that help you decide whether applying the Service Locator pattern to a particular component or layer within your application makes the most sense.

To reiterate, this is not a pattern that should be applied carte-blanche to all code. Not all code may benefit from using a Service Locator. To help you decide, start by dissecting the structure of your code into layers and components. For example, the layers in an MVC application are Models, Controllers and Views (in that order). The components can be any supporting library code that can stand on its own.

Once you’ve done this, the next question to ask of each is which part could benefit from the usage of a Service Locator vs. having explicitly defined class dependencies. To do this, there are a few considerations that help you decide:

  • What does an instance of a particular class best represent? Is it an object with a well defined API, or does the class better serve as a collection of somewhat related methods? (Loose API’s benefit from consuming a Service Locator.)
  • How many actual required dependencies are there? (Obj.’s with many dependencies benefit from a Service Locator)
  • Is it cumbersome to wire in a large set of dependencies? (Similar to above, put another way.)
  • How many “optional dependencies” are there? (Obj.’s with variable dependency sets benefit from a Service Locator)
  • How many required dependencies shared throughout all workflows of a given object? (Obj.’s with many dependencies benefit from a Service Locator.)
  • Is it anticipated that the number of dependencies might grow over time, or is it fixed? (Adding dependencies requires informing the consumer about how to wire new dependencies.)
  • How important is it that a particular class announce its dependencies in various method signatures? (More dependencies means more API introspection to determine how to wire an object.)
  • Is this piece of code at the center of many cross-cutting concerns? (Cross-cutting concern objects generally have lots of completely unrelated dependencies. As such, their API, like in point number 1, means little in terms of defining the objects actual role.)

In summary, if you find that if:

  • a layer of code or component has many dependencies that are not shared by all workflows of that particular consuming object;
  • that the number of these dependencies is large and capable of growing over time;
  • that there is not much importance on objects announcing their dependencies (via constructor injection, setter or interface injection);

then Service Location is by far a better solution than practicing dependency injection.

An Example/Exercise In Where I Make The Distinction

To better understand the considerations I’ve laid out above, I’ll demonstrate their application in an application’s architecture.

When dealing with an MVC application architecture, I prefer to practice service location for controllers, and dependency injection for models. So, how do I go about justifying that decision?

  1. Concerning controllers: The fact that a controller is an object and an action a method is inconsequential.

    Most actions could as easily do their job as a closure (like many Sintra style frameworks), or a function. While you get some benefits by being able to tuck away helper functionality in protected methods, the greatest driving factor for controllers as objects is simply that they can group contextually similar actions together into a class. Moreover, the public API of a controller is generally only ever interacted with via a dispatch framework. Rarely do developers instantiate and call controller actions without first going through some dispatcher object.

  2. Concerning controllers: Controller dependency sets are variable per controller.

    For any given action within a controller, it’s dependencies will vary. Given that you’ve grouped your User centric actions together, it goes without saying that while most might share some dependencies, it’s rare that every action will share all those dependencies. For example, a UserController::editAction might have a dependency on a UserForm object, but UserController::showAction might not:

  3. Concerning controllers: Injecting specific dependencies is generally outside the scope of a dispatcher.

    The job of a dispatcher is not trivial. Depending on the system, it could be parsing a HTTP route, and dissecting some information to determine what code to instantiate/invoke. Once it’s figured out what needs to be invoked, the last thing you want is your dispatcher is to have enough contextual understanding to make decisions on what specific services need to be injected into this particular controller. The following is a common dispatch workflow is non-complex frameworks:

  4. Concerning controllers: Controllers are very much tied to the stack that dispatches them.

    Controllers generally already have a dependency on the framework they are being dispatched from. Along the same lines of thinking, them understanding the type that $serviceLocator->get(‘db’); does not make them any less reusable since they are already dependent on a particular framework to dispatch them. (Unless you’re not using a framework, then this point is moot.)

  5. Concerning models: It is highly anticipated that models can be re-used outside of this particular application.

    That said, I find it important to be able to isolate dependencies and practice good DI in models. After all, they are most closely related to the business problem at hand, and since they are environment agnostic, they are a prime candidate for unit testing (whereas controllers are a better candidate for some form of UI or behavior testing). In addition, I find it important to see all dependencies up front when constructing models. Too many dependencies generally means it’s time to go back to the drawing board.

  6. Concerning models: The API of a model is something a developer (not a framework) interacts with.

    That being the case, the API needs to be able to effectively communicate any dependencies to the developer, without them having to consult large amounts of documentation.

Brief Parting Words

In short, contrary to a popular belief, Service Location is not always an anti-pattern. Patterns are not prescriptions, and service location is no exception. In some cases, using service location is beneficial, in others it is not.

Tagged , , , , , ,

  • http://pooteeweet.org Lukas

    First up I am not entirely clear on your definition of “auto-wiring”. I dont believe that a DIC must necessarily be able to auto discover dependencies. As a matter of fact imho such auto discovery can jeopardize some of very benefits of DI by effectively binding various services to one and only one other service with hard to oversee consequences when changing a service that is being dependent on in this way. But like I said I am not sure if “auto discovery” is what you mean with “auto-wiring” (which is what I understand the term is mostly used with) or if you just mean that the DIC must have the ability to create and inject dependencies that are configured (where auto discovery can be one of the methods for configuration).

    Indeed there are valid reasons that make using a service locator inside controllers sensible, the main one being that its convenient and that controllers do not tend to really benefit from DI itself as much as other classes. However you gloss over one important feature of a DIC and that is the ability to quickly determine what depends on what, which can be a very useful tool in managing larger code bases. Furthermore I firmly believe that as long as you make services lazy load connections that for the most part the performance overhead of optional dependencies isnt really a deciding factor. But of course at times it can become an issue and splitting up controllers just for this can become a maintenance annoyance.

    As for models I tend to believe they should not have many dependencies to begin with, as a result they tend not to need DI at all and therefore also do not need to be managed via a DIC.

    As you focus on the C and M, there are in practice also “helper services” which imho in general very much benefit from a DIC. These are where most of the “meat” of the business logic should reside. But in general the dependencies are very clearly defined and rarely optional. Finally they very much benefit from unit tests, which make them a prime candidate for DI and as they also tend to have deep structures of dependencies they are also best managed via a DIC.

  • http://stackoverflow.com/users/895378/rdlowrey Daniel Lowrey

    In short, contrary to a popular belief, Service Location is not always an anti-pattern. Patterns are not prescriptions, and service location is no exception. In some cases, using service location is beneficial, in others it is not.

    When is obscuring your class API with a Service Locator ever not an anti-pattern?

    In real life you wouldn’t build a house by transporting the entire hardware store (hopefully) to the construction site so you can access any parts you need (Service Locator). Instead, the foreman (__construct()) asks for the specific parts that will be needed for the House (Door and Window) and goes about procuring them. Your objects should function in the same way: they should ask only for the specific dependencies required to do their jobs.

    Giving the House access to the entire hardware store is *at best* very poor OOP style and at worst a maintainability nightmare.

  • http://ralphschindler.com/ Ralph Schindler

    @Lukas

    When I say “auto-wiring”, I don’t mean auto-discovery. Auto-discovery, I think we can agree, means how he DiC goes about building up the meta-data about a particular class and deriving its dependency list, for example, does it use Reflection to read the class’s signature, etc.

    With auto-wiring, I mean any post-construction workflows that are used to “wire” dependencies into a particular instance. This could be through setter injection, or using the interface “*Aware” injection convention, or it could be property setting or through Reflection-property setting. (For example, auto-wiring without auto-instantiation effectively means you have a Di Container that already has all instances needed to fulfill dependencies, or user provided factories that create these instances, and the container does the work of introducing those instances together to satisfy a dependency.)

    Furthermore I firmly believe that as long as you make services lazy load connections that for the most part the performance overhead of optional dependencies isnt really a deciding factor.

    There is, I’ve found, a different kind of overhead that goes largely ignored. It is the overhead of knowing and understanding how to seed/configure/maintain a meta-data based DI container, particularly in a language that is not strongly typed, or a framework that has lots of different coding conventions that are hard to code against. For example, a class that might have public function setFoo($foo) {}, what is $foo? Is it a string, or an instance of FooInterface that happens to be in the same namespace? This kind of disambiguation requires further configuration. Multiply that per every controller and you are now no longer programming as much as you are meta-programming. I chalk that up to overhead associated with maintenance of code as well.

    As for models I tend to believe they should not have many dependencies to begin with, as a result they tend not to need DI at all and therefore also do not need to be managed via a DIC.

    It’s been pointed out I should qualify “models” as “business objects”. That said, the kind of models I do create do have some, albeit a small amount, of dependencies. For example, a UserRepository might have as a dependency a UserMapper that might have a database connection as a dependency. Entities and value objects, on the other hand, should be dependency free.

    In summary, I believe when you, as a user, have to provide all the factories to a container for all your instances, and for example, that container injects itself into the factories, this is not enough to be called a dependency injection container (again, this is me being a pedant about the naming of such instances). I wouldn’t call Pimple, or instances like it a DiC. That’s not to say that I don’t think Pimple is one of the best things to come out with 5.3 though, I really love the problem it solves and the simplicity it does it with.

    As for the example, I find if you’re creating controller helpers, they generally are single tasked and just as controllers, can also resolve dependencies via service location. On the other hand, if they are business objects or services closely associated with your “models”, the I agree they should practice good DI and use conventions that can be used with at DiC (like constructor injection).

  • http://ralphschindler.com/ Ralph Schindler

    @Daniel Lowrey

    In “real life” I don’t build houses, I build software. :) Your abstract and context-free example is lost on me.

    I don’t apply the same general rules to all elements of software. In some cases a particular pattern is the right fit when all things are considered: language, platform, framework, performance, maintenance, testability, team preferences and team philosophy, all amongst others I’ve not mentioned.

  • Stephane

    It’s great to see differing opinions on this. I really appreciated how you went through standard code instead of just staying in hypotheticals. I’m just getting into OO concepts and I would love to see more how you wire your models together. I’m especially curious about what $this->view->stuff does. I’ve seen others use this, but I’ve never seen the end result. A little off-topic, but I hope my question is helpful to others in the same predicament as I am.

  • cryptocompress

    one thing i missed: “newables/stateful objects vs injectables/stateless objects”
    very nice summary otherwise. thank you!

  • Kevin Foster

    Your list of considerations for deciding when to use which seems more like a list of justifications for using a service locator.

    In response to your example points:

    Point 1.
    If I want to unit test my controllers (cleanly) I would need to instantiate my controller. I really wouldn’t want to have to include a dispatcher in my tests. My tests should only execute controller code. Any dependencies should be mocked.

    Point 2.
    I agree that a controller might have many more dependencies than an single action requires but I don’t think this is a good reason to resort to using a service locator. There are other ways to solve this problem if it does in fact become a problem. You could always split the functionality into multiple controllers. Which isn’t nice but I think its nicer than using a service locator.

    Point 3.
    The dispatcher doesn’t need have any knowledge of the controllers dependencies. The DiC can resolve dependencies of a controller.

    Point 4.
    Yes the controller will always be tied in someway to the dispatcher. In your example they are only tied in that the dispatcher requires the controller to implement a method for each action, and that method must accept a route params array. If the dispatcher design you are using has many more expectations of your controllers then I would say that is an issue with the design of the dispatching process.

    Point 5.
    I agree with most of what you say except the part about not needing to test controllers. I believe controllers contain important logic not related to the UI that can and should be unit tested.

    Point 6.
    I agree but I think this also applies to controllers if you are unit testing them.

    I am not sure if the service locator is an anti pattern or not but I do believe that classes that declare their dependencies explicitly are much easier to work with and require less documentation.

  • http://ralphschindler.com/ Ralph Schindler

    @Kevin Foster

    Your list of considerations for deciding when to use which seems more like a list of justifications for using a service locator.

    That is exactly what this is. Before you use any particular pattern, you should justify its usage. I do this for deciding when to use a dependency injection container as well.

    As for your points:

    Point 1.
    By and large, a controllers primary job is to parse the environment and marry that information gleaned from it for the purpose of coordinating model and view interactions. I personally don’t like a whole lot of abstraction around this, so you’ll generally find interactions with $_POST or $_GET inside my controllers. That said, I don’t see a ton of value in unit testing controllers. What I do find value in is integration/behavior testing controllers. Sometimes that might utilize phpunit to achieve this task, sometimes it might be something more like selenium.

    Also, it is a fallacy that all service locators preclude dependencies swapping or mocking. This is largely a feature of your service locator, depending on your implementation. If your consumers hint on a service locator interface, then everything is mockable, and just as easily as using dependency injection of the dependencies.

    Point 2.

    … but I don’t think this is a good reason to resort to using a service locator.

    It’s clear to me you already have decided that service locators are evil. As someone who has written a full featured DI Container, I’ve learned through other developers usage of it in their applications, that for PHP, it’s not always the best tool for the job.

    Point 3.
    Right, but now your application dispatcher has to know about and interact with a DiC, which I could argue comes at a large cost in PHP.

    Point 5.
    See point 1.

    Point 6.
    I agree, if your goal for any code is extensibility and sharability, then using/requiring a service locator as a dependency is the wrong tool for the job. That said, in all my time as a developer, I’ve never expected my controllers or application-layer service (cross cutting) classes to be extended or shared, they are primarily there to facilitate that particular application. My models, on the other hand, I have shared and have been extended.

  • http://stackoverflow.com/users/895378/rdlowrey Daniel Lowrey

    @Ralph, I can appreciate your statement. Please elaborate on when you feel it’s appropriate to obscure your class API with a potentially infinite number of dependencies via a Service Locator instead of providing the class exactly what it needs.

  • http://ralphschindler.com/ Ralph Schindler

    @Daniel Lowery

    Since in the normal workflow the controller API is primarily consumed by a dispatcher, its API is inconsequential to me. Since a machine is interacting with these classes more than a developer, I find it more beneficial to make their API consistent and simple. For all intents and purposes, the API for all my controllers in a given application can be expressed like this: $response = (new SomeController($serviceLocator))->action($request);

    So, to summarize, in that particular situation (controllers), I find more value in consistency and simplicity of the controller API than I do in the expressiveness of its API announcing its dependencies.

    I could turn this around on you, also. Explain why you feel it’s appropriate to obscure a controllers API by enforcing a myriad of different instantiation and wiring signatures for a set of classes that are largely going to be interacted with not by a developer, but by another piece of code. Or further, why shift the onus of instantiation and wiring of a controller onto a DI container when that effectively means for each new controller you write, you’ll also have to meta-program the configuration into your DI container (or compile out some kind of class meta-data)?

  • http://stackoverflow.com/users/895378/rdlowrey Daniel Lowrey

    @ Ralph Schindler
    Your logic is seductive and I can see its appeal, but I find it particularly insidious (not because of any particular intent of yours) and detrimental to quality craftsmanship.

    The “as long as it makes sense to the machine code” argument could likewise be used to justify the use of any frowned-upon practice … go-to statements everywhere, global everywhere, who cares! The
    machine understands it and the code works as planned! Yet you’re using classes. Why? Presumably because you see the maintainability benefits of OOP. If we only care about the machine’s ability to
    understand code, why do we bother with anything but procedural spaghetti? After all, it would certainly be much easier to use global state everywhere. We don’t do these things for several reasons, namely
    because …

    1. Maintainability matters!

    At a certain point your code MUST be revisited by and comprehensible to a human being. You might understand what the controller does and how it works, but another person will not have this benefit.
    Clear APIs are a REQUISITE for enterprise-level development. “I don’t care because the computer understands what’s supposed to happen” simply does not hold water. It’s imperative that I don’t have
    to open up the innards of your code and grok it completely to know how it works. The API should tell me everything I need to know up front. If it doesn’t, I’ll forever refer to you as “that last guy who was here who wrote
    unmaintainable code and is the reason I spend half of every day trying to understand what he was thinking.” Yes, YOU have privileged knowledge about what the controller does and what dependencies it requires
    to do its job because you wrote it. The person who maintains your code after you does not.

    2. Loose-Coupling matters!

    Service Locator means you’ve tightly coupled your class to an unnecessary faux-dependency. What happens when Symfony 3 comes out? Oh no! I have to change ALL of my classes or I can’t upgrade
    because this damn service container is coupled to EVERYTHING!

    Even if your Service Locator is programmed to an interface and its API doesn’t change, you’ve tied your classes unnecessarily to an object on which they don’t have a real dependence. This limits
    your options going forward because now the only way to get away from the Service Locator is to refactor every god-forsaken class in your application to which you’ve coupled it. The
    Service Locator is like a cancer — once you start using it in your application the costs of removing it are generally too large to overcome without refactoring everything. This is a good thing if you’re
    a Service Locator vendor but a very bad thing for someone hoping to write portable code.

    How portable is my domain logic if it’s only coupled to relevant domain logic? Very. Once I introduce the Service Locator I’m forced package it with everything. My code can’t go anywhere without dragging
    along this other object that’s not even semantically related. Coupling, your code’s delivery mechanism to actual domain logic is poor design.

    3. Testability matters!

    Regarding unit-testing, I really only see three possibilities when people espouse the use of Service Locators:

    – They’re not unit-testing their code
    – They’re not unit-testing their code correctly
    – They’re seriously masochistic

    Successful unit testing requires that you test units of code in complete isolation from all outside influences. This means that if your class depends on a Service Locator you must set up a full-fledged
    instance for every test. Can this be done with modern test frameworks? Yes. The issue is that your test code becomes instantly more complicated when you have to provision them with a Dependency Lookup
    instance. It’s so much simpler to mock dependencies directly instead of mocking the Service Locator to return a dependency that does what you need it to in the context of that test.

    Much worse … once you generate a mock test dependency by way of a locator you’ve ceased testing your interface and moved into the realm of testing implementation details.
    You can’t test code that uses a locator without knowing intrinsically how that code works. Fundamental to the concept of unit-testing is that you SHOULDN’T TEST IMPLEMENTATION DETAILS. No sane person
    tests this way because you’ll be forced to re-write your tests over and over.

    4. Shortcomings of your DIC !== Shortcomings of IOC

    You argue that the rigamarole of defining all sorts of meta data to allow proper injection of controller dependencies far outweighs the benefits. First, I couldn’t disagree more. Second, an injection
    container that makes it difficult to implement IOC in your classes is not a reason to avoid Inversion of Control; it’s a reason to avoid that injection container. That point has no bearing on the efficacy
    of IOC or Service Locators.

    So yes, if you’re in a position where (1) you’re the only person who ever has to see or maintain your code, (2) you’re 100% sure you’ll never need to change your delivery mechanism, (3) you’re not concerned
    with rigorous unit-testing and (4) your DIC interface makes properly implementing IOC complete drudgery … sure, the Service Locator use in controllers probably won’t hurt you.

  • http://stackoverflow.com/users/895378/rdlowrey Daniel Lowrey

    I want to apologize for stray line-breaks, comma splices etc in the above comment. Notepad++ is not the best word processor for blog comments. I also appreciate your viewpoints — I just disagree. Upon re-reading I was a little concerned that I came off as combative (which is not the intent). I would add that ultimately, code that works is more valuable than academically perfect code that doesn’t. So, if faced with something that works with a Service Locator versus something that’s totally 100% IOC’d and perfect but you don’t have time to implement, you have to go with functional code. I see enough benefit in full IOC to eschew the negatives that come with a Service Locator entirely. We may, however, just have to agree to disagree on this point.

  • http://granula.github.com Franklin

    Great article!

    Look at this simple and good DI implementation: Inversion library

  • Pingback: PHP Digest: PHP Goes to Mobile Platforms, Bitbucket Update, 5 Reasons to Try Codeception and Much More | Zfort Group Blog

  • Kate

    Thanks for sharing the great news! It was included into a digest of the hottest and the most interesting PHP news: http://www.zfort.com/blog/php-digest-october-22/

  • Pingback: PHP Digest: PHP Goes to Mobile Platforms, Bitbucket Update, 5 Reasons to Try Codeception and Much More | Zfort Group Blog

  • http://objectic.cc/ Niko Kivelä

    I would like to think that application code (controllers and helpers) are dependent of library code (models) and not way around. For modules aka. business objects DiC does this just fine. This includes all the repositories, factories, mappers and services you might have.

    Controller’s responsibility is to interpret users input and populate views whose responsibility is solely output and format the data. Controller pulls out dependencies from library. This behaviour can change according user’s input from request to request. This is no job for a DIC since it should contain no logic and in this matter, Controllers are quite happy to use ServiceLocator for satisfying its own purpose.

What's this?

You are currently reading DI, DiC, & Service Locator Redux at Ralph Schindler.

meta