2

I've read a couple of articles my Mark Seeman on Dependency Injection, specifically the reasons for avoiding the Service Locator pattern:

Basic idea behind Service Locator being problematic is that it can fail at runtime:

public class OrderProcessor : IOrderProcessor
{
    public void Process(Order order)
    {
        var validator = Locator.Resolve<IOrderValidator>();
        if (validator.Validate(order))
        {
            var shipper = Locator.Resolve<IOrderShipper>();
            shipper.Ship(order);
        }
    }
}

var orderProcessor = new OrderProcessor();

// following line fails at compile time if you 
// forget to register all necessary services - and
// you don't have a way of knowing which services it needs

orderProcessor.Process(someOrder);

But this means that the Composition Root must not only resolve all dependencies at startup, but actually instantiate the entire object graph, or otherwise we still wouldn't know it all the necessary dependencies have been registered:

private static void Main(string[] args)
{
    var container = new WindsorContainer();
    container.Kernel.Resolver.AddSubResolver(
        new CollectionResolver(container.Kernel));

    // Register
    container.Register(
        Component.For<IParser>()
            .ImplementedBy<WineInformationParser>(),
        Component.For<IParser>()
            .ImplementedBy<HelpParser>(),
        Component.For<IParseService>()
            .ImplementedBy<CoalescingParserSelector>(),
        Component.For<IWineRepository>()
            .ImplementedBy<SqlWineRepository>(),
        Component.For<IMessageWriter>()
            .ImplementedBy<ConsoleMessageWriter>());

    // Everything must be resolved AND instantiated here
    var ps = container.Resolve<IParseService>();
    ps.Parse(args).CreateCommand().Execute();

    // Release
    container.Release(ps);
    container.Dispose();
}

How feasible is this in a real world application? Does this really mean you are not supposed to instantiate anything anywhere outside the constructor?

(Additional info)

Say you have a service which is supposed to handle incoming connections from multiple measurement devices of some kind (different connection types, protocols, or different versions of the same protocol). Whenever you get a new connection, service is supposed to construct a "pipeline" from the input port, through fifo buffers, to many parsers specific to that device type, ending with multiple consumers for various parsed messages.

Composing these object graphs in advance is something that doesn't seem possible on application startup. Even if it can be delayed, I still don't see how it's possible to get an early(-er) indication that object graph construction will fail.

This seems to be the main problem with service locators, and I don't see how to avoid it:

In short, the problem with Service Locator is that it hides a class' dependencies, causing run-time errors instead of compile-time errors, as well as making the code more difficult to maintain because it becomes unclear when you would be introducing a breaking change.

Lou
  • 4,244
  • 3
  • 33
  • 72
  • 2
    *How feasible is this in a real world application?* Quite feasable http://blog.ploeh.dk/2011/03/04/Composeobjectgraphswithconfidence I do it all the time; it's not an abstract exercise, but a description of how I (and others) have built applications for decades. – Mark Seemann Oct 27 '15 at 11:31
  • 1
    [Here is another reason](http://yacoubsoftware.blogspot.com/2015/10/object-composability-another-reason-why.html) why the service locator is an anti-pattern. It limits Object Composability. – Yacoub Massad Oct 27 '15 at 13:52
  • We can do compile-time or runtime analysis on service locator too. As far as ensuring that a service is configured, there's not much difference between a DI container and a SL registry. – ZhongYu Oct 27 '15 at 18:12
  • @MarkSeemann: well I presumed *you* use it all the time :-). I am aware that I have to change my mindset to see how to make DI applicable to my app types, but that's my point: I am not sure how. For example, in a server app accepting connections from hundreds of devices of different type/protocols, I must have a way of managing the lifetime of a single device "pipeline", from the incoming port, through various parsers (instantiated depending on device type/protocol), to end consumers. This is a rather "dynamic" part of the app's object graph, and I cannot see how to possibly "root it"? – Lou Oct 28 '15 at 08:36
  • @MarkSeemann: I have your book, btw, I have read it a couple of years ago and since then I am using constructor injection pretty much exclusively, but there are parts of my apps which look like smaller, "context dependent" composition roots if that makes sense, and these are parts which are postponed long into runtime. I should probably take another look at the book also. – Lou Oct 28 '15 at 08:42
  • Can you use an Abstract Factory? http://stackoverflow.com/a/1945023/126014 – Mark Seemann Oct 28 '15 at 08:46
  • Or one of the Role Hints selectors? http://stackoverflow.com/a/22705794/126014 – Mark Seemann Oct 28 '15 at 08:48
  • @MarkSeemann: of course, but what should the factory do? Create the appropriate object graph based on input parameters in runtime? – Lou Oct 28 '15 at 08:59
  • Yes, but I prefer the *Partial Type Name Role Hint* solution if at all possible. – Mark Seemann Oct 28 '15 at 09:16
  • @MarkSeemann: but this brings me back to where I started: all these suggestions seem prone to runtime failure. Especially role hints, which seem pretty similar to service locators: I don't see how you would ensure that a certain role exists, any simpler than you would have to do for a service locator. With metadata role hints, you've gone from a one-and-only "composition root" to scattering attributes all over the code. Type Name Role Hints approach also promises to "just work" by convention, but it seems even harder to check if services exist - the decision is made inside the factory method? – Lou Oct 28 '15 at 09:34
  • At run-time, things can fail. There are no guarantees. None of my suggestions, however, are Service Locators http://blog.ploeh.dk/2010/11/01/PatternRecognitionAbstractFactoryorServiceLocator – Mark Seemann Oct 28 '15 at 09:58
  • @MarkSeemann: *"At run-time, things can fail. There are no guarantees."* - that's the last thing I would expect to hear from you. So, it started with *"the problem with Service Locator is that it hides a class' dependencies, causing **run-time errors** instead of compile-time errors"*, through suggestions to use metadata and name-based run-time lazy resolving, to "There are no guarantees"? Of course these are not Service Locators, but IIRC it's you who said the problem with the Service Locator pattern is *where* it is used, not what the method call looks like. – Lou Oct 28 '15 at 10:14
  • Because now it seems the answer to my question is that it *is* feasible for people to make applications without using Service Locators explicitly, but alternative approaches don't make any guarantees about what happens in runtime? And your remark that Roles are not Service Locators merely avoids the answer to the actual underlying question: I don't see how you would ensure that a certain role exists, any simpler than you would have to ensure that a service was registered with a service locator. – Lou Oct 28 '15 at 10:18
  • 1
    If you can't compose a complete object graph of Services at startup-time, it must be because some run-time value is required; at least, I can't think of any other reason. Run-time values are input, and you must assume that all input is evil. On evil input, things will fail. If the network is down, things could fail. And so on... – Mark Seemann Oct 28 '15 at 10:35
  • But presuming the input is not evil, and the network is running, and we are concentrated solely on *class dependencies* instead, because this is something we **can** influence, I still don't see how Roles (or lazily invoked factory methods) fix Service Locator issues. To use your [`OrderProcessor` example](http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/), if I don't have its source code, I cannot know which services it uses. But similary, wherever I inject a Role-based selector instead, I still have no way of knowing which Role will be required at runtime. – Lou Oct 28 '15 at 10:59
  • Ok, I think I got it, what confused me is that a Service Locator provides *dependencies*, while Factories (even if Role-based) are dependencies themselves, regardless of how they are injected. So, if the graph needs to be constructed based on run-time input, then it simply cannot be instantiated at application's composition root, but this still means it should get instantiated the same way in run-time (by injecting dependencies, instead of using a Service Locator). Composition root provides a *better* way to generate an object graph, but doesn't mean you'll always be able to do it on startup. – Lou Oct 28 '15 at 11:04
  • @MarkSeemann: also, if you get some time to formulate your comments into an actual answer, I believe it would be the answer to my question. – Lou Oct 28 '15 at 11:08
  • In theory you can construct the complete graph at startup, with all services instantiated and mapped to all possible run-time inputs. But it's not very important *when* actual objects are instantiated. The important thing is, the decision of what services are to be instantiated is entirely coded in the startup code. – ZhongYu Oct 28 '15 at 14:15

1 Answers1

4

But this means that the Composition Root must not only resolve all dependencies at startup, but actually instantiate the entire object graph

If you apply Pure DI (i.e. applying the Dependency Injection pattern, but without a DI container) you'll get compile-time support out of the box. With a DI container you will have to do these checks at runtime, but that doesn't mean that you have to do this during start-up, although I would say it is preferred. So if checking the container's configuration at startup doesn't cause performance issues, you should do this. Otherwise you can move this verification step to a unit test.

How feasible is this in a real world application?

This is completely feasible. I build big applications with often hundreds to over a thousand services registered in the container and I always verify (and diagnose) my container's configuration, which prevents many common configuration mistakes that are very easy to make and very hard to track down.

Does this really mean you are not supposed to instantiate anything anywhere outside the constructor?

It is your composition root that is responsible for creating your services; that doesn't mean per se that all services should be created during startup, because you can delay the creation of parts of your object graph until runtime. Still however, my preferred way of working is to make all registered services singleton (one instance for the during of the application). This makes it really easy (and cheap) to create all services during application startup, and forces you into a more strict model where SOLID violations and other DI bad practices popup much sooner.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • +1 Thanks. The point is (I have posted a similar comment to @MarkSeeman above), what if I have places where the object graph depends a lot on its context? I.e. in a server app accepting connections from hundreds of devices of different type/protocols, I must have a way of managing the lifetime of a single device "pipeline", from the incoming port, through various parsers (instantiated depending on device type/protocol), to end consumers. I do use constructor injection just about everywhere, but there are some places where I create smaller object graphs dynamically during runtime. – Lou Oct 28 '15 at 08:44
  • @LousyCoder, can't you use abstract factories to construct object graphs at runtime? – Yacoub Massad Nov 01 '15 at 23:00