Dependency Injection VS Service Location

Dependency Injection vs Service Location

Because the class is now being injected with an IoC container, then why not use it to resolve all other dependencies too?

Using the service locator pattern completely defeats one of the main points of dependency injection. The point of dependency injection is to make dependencies explicit. Once you hide those dependencies by not making them explicit parameters in a constructor, you're no longer doing full-fledged dependency injection.

These are all constructors for a class named Foo (set to the theme of the Johnny Cash song):

Wrong:

public Foo() {
this.bar = new Bar();
}

Wrong:

public Foo() {
this.bar = ServiceLocator.Resolve<Bar>();
}

Wrong:

public Foo(ServiceLocator locator) {
this.bar = locator.Resolve<Bar>();
}

Right:

public Foo(Bar bar) {
this.bar = bar;
}

Only the latter makes the dependency on Bar explicit.

As for logging, there's a right way to do it without it permeating into your domain code (it shouldn't but if it does then you use dependency injection period). Amazingly, IoC containers can help with this issue. Start here.

Explain Dependency Injection vs Service Location when using IoC containers

The point of dependency injection is that you can do

public Foo(IBar bar) 
{
this.bar = bar;
}

and decouple the class Foo from the Bar class. You then use StructureMap, Unity, or any other of the 20+ dependency injection containers to configure which class or instance you want to use for each contract (=interface, optionally plus name) and let the DI container resolve all dependencies.

You do that because it allows you to unit test your class Foo effectively by mocking its dependencies. You usually do not explicitly inject the dependencies yourself.

In my opinion, dependency injection usually works best, if you only use it as a one-time thing. That is, you resolve your main application service at the beginning of your appliaction including all of its dependencies and then use the initialized network of service objects while the application is running.

You avoid the service locator (which is your second code example) because 1. it ties you to a specific DI container and 2. you are hiding the dependency to IBar.

Dependency injection or service location?

It is service locator. There are several of ways to use dependency:

  • aggregation (example case)

  • composition

    • DI in constructor, mandatory (could be injected using SL on upper level)

    • DI in property, optional (could be injected using SL on upper level)

What to choose depends on many factors, for example whether it is stable or unstable dependency, whether you need to mock it in tests or not etc. There is good book on DI called 'Dependency Injection in .NET' by Mark Seemann.

Service Locator vs Constructor injection performance

Before I'll talk about the performance difference between the two approaches, I need to set the stage and talk about the Service Locator anti-pattern, because not every callback to the DI Container is an implementation of Service Locator.

Calls to the DI Container (or an abstraction over it) should be prevented from application code, e.g. inside your MVC controllers, or code part of your business layer. Callbacks from these parts of your code base can be considered examples of Service Locator.

Callbacks from parts of your application's startup path, a.k.a. the Composition Root, on the other hand, are not considered to be Service Locator implementations. That's because the Service Locator pattern is more than the mechanical description of a Resolve API, but rather a description of the role it plays in your application. These calls to the Container from inside the Composition Root are fine, beneficial, or even required for your application to function. Therefore, for the remaining part of my answer, I rather refer to "callback to the DI Container" rather than "using the Service Locator pattern."

When it comes to performance, there are many things to consider. It would be impossible for me to mention every possible performance bottleneck and tweak, but I'll mention the few things I think are most worthwhile to talk about in the context of your question.

Whether or not the lazy resolving of dependencies by calling back into the container is faster than constructor injection depends on a lot of factors. In general, I would say that in both cases performance is typically irrelevant as object composition would unlikely be your application's performance bottleneck. In most cases I/O takes up the bulk of the time. Time is typically better spent in optimizing I/O—it results in better performance gains with less investments.

That said, one thing to realize is that DI Containers are typically highly optimized and can do optimizations during compilation of the generated code that composes your application's object graphs. But these optimizations are thrown out when you start to break up object graphs by calling back into the container lazily. This makes constructor injection a more optimized approach, compared to breaking an object graph in pieces and resolving them one by one.

If I use Simple Injector—the DI Container that I maintain—as an example, it does quite aggressive optimizations on generated Expression trees before it starts compiling them. Those optimizations include:

  • Reducing size of compiled code by reusing compiled code within the graph.
  • Optimizing the request of scoped components within the graph, by caching them in variables (closures) inside the compiled method. This prevents duplicate dictionary look-ups.

Your mileage will obviously vary, but most DI Containers perform some kind of optimization. I'm unsure what kinds of optimizations the built-in ASP.NET Core DI Container applies though, but AFAIK its optimizations are limited.

There is overhead in calling your Container's Resolve method. At the very least it causes a dictionary lookup from the requested type to the code that is able compose the graph for that type, while dictionary look-ups tend not to happen (that much) for resolved dependencies. But in practice calls to Resolve tend to have some validity checks and other required logic that adds overhead to such call. This is another reason why constructor injection a more optimized approach, compared to doing callbacks.

Modern DI Containers are usually optimized so they can resolve big object graphs with ease (although with some containers there is a limit in the size of the object graph, although that limit is typically pretty big). Their overhead compared to creating those same object graphs manually (using plain old C#) is usually minimal (although differences and exceptions exist). But this only works if you follow the best practice to keep your injection constructors simple. When injection constructors are simple, it doesn't matter if you inject dependencies that are only used part of the time.

When you fail to follow this best practice, for instance by having injection constructors that callback to the database or perform some logging to disk, performance of object graph resolution can slow down considerably. This can be certainly painful when you're dealing with components that aren't always used. This seems to be the context of your question. Here's an example of a problematic injection constructor:

// This Injection Constructor does more than just receiving its dependencies.
public OrderShippingService(
ILogger logger, IConfigurationProvider provider)
{
// Storing the incoming dependency; this is fine.
this.logger = logger;

// Here it starts using its dependencies; this is problematic.
logger.LogInfo("Creating OrderShippingService.");
this.config = provider.Load<OrderShippingServiceConfig>();
logger.LogInfo("OrderShippingService Config loaded.");
}

My advise, therefore, is: follow the "simple injection constructors" best practice and make sure that injection constructors do no more than receive and store their incoming dependencies. Do not use dependencies from inside the constructor. This practice also helps when dealing with dependencies that are only used part of the time, because when those dependencies are fast to create, the problem goes away and using constructor injection will typically still be faster compared to doing callbacks.

On top of that, there are other best practices that should be followed, such as the Single-Responsibility Principle. Following it prevents constructors to get many dependencies and prevents the Constructor Over-Injection code smell. Object graphs that contain classes with many dependencies tend to become much bigger in size and, therefore, slower to resolve. This best practice doesn't help when dealing with those sometimes-used dependencies, though.

It might be the case, however, that you're unable to refactor such slow constructor, which requires you to prevent it to be eagerly loaded. But there are other cases in which eager loading can cause problems. That can happen, for instance, when your application uses Composites or Mediators. Composites and Mediators typically wrap many components and can forward an incoming call to a limited subset of them. Especially a Mediator, which typically forwards the call to a single component. For instance:

// Component using a mediator abstraction.
public class ShipmentController : Controller
{
private readonly IMediator mediator;

public void ShipOrder(ShipOrderCommand cmd) =>
mediator.Execute(cmd);

public void CancelOrder(CancelOrderCommand cmd) =>
mediator.Execute(cmd);
}

In the code above, the IMediator implementation should forward the Execute call to a component that knows how to handle the supplied command. In this example the ShipmentController forwards two different command types to the mediator.

Even with simple injection constructors, the previous example might cause performance problems when the application contains hundreds of those 'handlers' in case those handlers contain deep object graphs by themselves and are all recreated each time ShipmentController is composed.

The following implementation demonstrates these performance issues:

// I'm using C# 9 record type syntax here, because that makes the example succinct
record Mediator(IHandler[] Handlers) : IMediator
{
public void Execute<T>(T command) =>
Handlers.OfType<IHandler<T>>().Single().Execute(command);
}
}

In this example, all handlers are eagerly created before Mediator is, and injected into the Mediator's constructor, while a call to Execute just picks one from the list. This could lead to performance issues when there are many handlers that contain many dependencies of their own. This is because in order to execute one handler, all handlers with their dependencies need to be constructed. Not ideal.

To prevent this performance problem, calling back into the DI Container is an option to consider. It doesn't require the Service Locator anti-pattern, though, because the Mediator implementation (and, therefore, the callback) should reside inside your Composition Root. A possible IMediator implementation could look like this:

// As long as this implementation is placed inside the Composition Root,
// this is -not- an implementation of the Service Locator anti-pattern.
record Mediator(IServiceProvider Container) : IMediator
{
public void Execute<T>(T cmd) =>
Container.GetService<IHandler<T>>().Execute(cmd);
}

In this case, only the relevant handler is requested from the DI Container—not all of them. This means that the DI Container at this point, only creates the object graph for that particular handler.

In all cases, however, you should prevent calling back to the DI Container from within application code. I would even argue not to inject a Lazy<T> for a conditionally used dependency, even though some DI Containers have support for this. This only complicates the consumer's code, its tests, and makes it easy to forget to apply Lazy<T> to all constructors for that dependency.

Instead, creating a Proxy would be a better approach. That proxy would live inside the Composition Root and would either wrap a Lazy<T> or call back into the Container:

record DelayedDependencyProxy(IServiceProvider Container) : IDependency
{
private IDependency real;

public object SomeMethod()
{
if (real is null)
real = Container.GetService<RealDependency>();

return real.SomeMethod();
}
}

This Proxy keeps the consumers of IDependency clean and oblivious to the use of any mechanism to delay the creation of the dependency. Instead of injecting RealDependency into consumers of IDependency, you now inject DelayedDependencyProxy.

One last, but important note: do prevent premature optimizations. Prefer constructor injection over container callbacks even if container callbacks are faster. If you suspect any performance bottlenecks by constructor injection: measure, measure, measure. And do verify if the bottleneck really is in object composition itself, or in the constructor of one of your components. And if fixed, verify that this gives a performance boost significant enough to justify the increased complexity it causes. A performance win of 1 millisecond is not significant for most applications.

What is the actual difference betwen a service locatior and a dependency injection?

This code sample applies the Dependency Injection principle:

public class UserService : IUserService
{
private IUserRepository repository;

// Constructor taking dependencies
public UserService(IUserRepository repository)
{
this.repository = repository;
}
}

This code sample uses the Service Locator pattern:

public class UserService : IUserService
{
private IUserRepository repository;

public UserService()
{
this.repository = ObjectFactory.GetInstance<IUserRepository>();
}
}

And this is an implementation of the Service Locator pattern:

public class UserService : IUserService
{
private IUserRepository repository;

public UserService(Container container)
{
this.repository = container.GetInstance<IUserRepository>();
}
}

And even this is an implementation of the Service Locator pattern:

public class UserService : IUserService
{
private IUserRepository repository;

public UserService(IServiceLocator locator)
{
this.repository = locator.GetInstance<IUserRepository>();
}
}

The difference is that with Dependency Injection, you inject all dependencies a consumer needs, into the consumer (but nothing else). The ideal way of injecting it is through the constructor.

With Service Locator you request the dependencies from some shared source. In the first example this was the static ObjectFactory class, while in the second example this was the Container instance that was injected into the constructor. The last code snippet is still an implementation of the Service Locator pattern, although the container itself is injected using dependency injection.

There are important reasons why you should use Dependency Injection over Service Locator. This article does a good job explaining it.

Dependency injection - trying to avoid using a service locator

I want to try and avoid using a service locator

Great, because the Service Locator is an anti-patttern.

don't register all the types in the startup.cs file.

You should do your registrations in one single 'area' of your application: the start-up path. This area is commonly referred to as the Composition Root (the place where object graphs are composed).

I don't think this is right that all these internal types are referenced in the main startup.cs

No matter how you design it, the startup assembly is the most volatile part of the system and it always depends on all other assemblies in the application. Either directly or transitively (through another referenced assembly). The whole idea of Dependency Injection is to minimize the coupling between components and the way to do this is to centralize coupling by moving it to the Composition Root. By making types internal however, you are decentralizing object composition and that limits your flexability. For instance, it becomes harder to apply decorators or interceptors for those registered types and control them globally. Read this question and its two top voted answers for more information.

I don't register all the types

The concern of having a Composition Root that is too big is not a valid one. One could easily split out the Composition Root into multiple smaller functions or classes that all reside in the startup assembly. On top of that, if you read this, you'll understand that registering all types explicitly (a.k.a. "Explicit Register") is typically pointless. In that case you're probably better off in using DI without a Container (a.k.a. Pure DI). Composition Roots where all types are registered explicitly are not very maintainable. One of the areas a DI Container becomes powerful is through its batch-registration facilities. They use reflection to load and register a complete set of types in a few lines of code. The addition of new types won't cause your Composition Root to change giving you the highest amount of maintainability.

I don't want to create all these builder classes in advance as I might not need to use them and creating them is a bit heavy

Creation of instances should never be heavy. Your injection constructors should be simple and composing object graphs should be reliable. This makes building even the biggest object graphs extremely fast. Factories should be reduced to an absolute minimum.

TLDR;

  • Register or compose your object graphs solely in the Composition Root.
  • Refrain from using the Service Locator anti-pattern; Whole applications can (and should) be built purely with Constructor Injection.
  • Make injection constructors simple and prevent them from doing anything else than storing their incoming dependencies.
  • Refrain from using factories to compose services, they are not needed in most cases.


Related Topics



Leave a reply



Submit