This post will briefly describe when it is ok to reference a container in your application.
What is a Container?
A container is an object that you delegate the responsibility of creating and managing your object graphs and object lifetimes.
What is a Service?
A Service is an object that exposes one or more methods.
What is the Service Locator Anti-pattern?
The best description of Service Locator Anti-pattern can be found on Mark Seemann’s blog here.
A common example is a central, global, static reference to a container that is accessible from anywhere in the code. The service locator is used to find instances of services. Your code can reference this service locator from anywhere and can call into it at any time.
E.g.:
public class BusinessObject {
public void Method() {
var service = ServiceLocator.Resolve<IService>();
}
}
Code that references the service locator cannot run without the service locator and the service locator hides a classes dependencies.
If you’re not supposed to reference the container all over the place how do you resolve dependencies within your classes?
You resolve dependencies using the Dependency Inversion Principle; injecting dependencies into each object.
Surely you must have some references to the container?
You have to reference the container to configure it and to Resolve()
instances from it. You have to do some Service Locator type activity and there is no escape from that fact.
Frameworks such as Microsoft’s MVC framework hide the fact that the container is acting as a service locator.
IDependencyResolver
Defines the methods that simplify service location and dependency resolution.
You cannot avoid Service Location; you can carefully govern and control when and where it happens.
Where is it ok to reference the container?
You can reference the container anywhere in the Composition Root
What is the Composition Root?
The composition root is the logical place that you configure the container.
A common misconception is that the Composition Root has to be in one file. This is not strictly true, but it does need to be logically separated from the rest of your solution.
- It can be spread across multiple files
- It can be a folder filled with classes
- It can be a namespace filled with classes
- It should contain all the code that references the container
As Mark Seemann advises here, the Composition Root should be
As close as possible to the application’s entry point.
When is it ok to reference the container?
The main driver for needing a hard reference to the container is to dynamically resolve objects based on data that is only known at run-time.
As mentioned already an example of this is the MVC framework that will resolve an object graph for each request based on the route of the request.
The Golden Rule
A class that references the container should not return a service. As stated above a service is a class that exposes one or more methods. A class that references the container should return the result of one or more service method calls or nothing (e.g. void
, null
, etc.).
Sticking to this rule effectively means your classes are acting as a proxy.
If your class references the container and returns a service then it is more akin to a factory and may be acting as a Service Locator.
What does a good example look like?
public sealed class EventPublisher : IEventPublisher {
private readonly Container container;
public EventPublisher(Container container) {
this.container = container;
}
public void Publish<TEvent>(TEvent eventMessage) where TEvent : class, IEvent {
var subscriber = container.GetInstance<ISubscriber<TEvent>>();
subscriber.Handle(eventMessage);
}
}
Another good example?
sealed class QueryProcessor : IQueryProcessor {
private readonly Container container;
public QueryProcessor(Container container) {
this.container = container;
}
public TResult Process<TResult>(IQuery<TResult> query) {
var handlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult));
dynamic handler = container.GetInstance(handlerType);
return handler.Handle((dynamic)query);
}
}
What does a mistake look like?
public class LogResolverService : ILogResolverService {
private readonly Container container;
public LogResolverService(Container container) {
container = container;
}
public ILogger<T> Resolve<T>() {
return this.container.GetInstance<ILogger<T>>();
}
}
Wrapping up
Referencing the container is definitely not the Service Locator Anti-pattern if you stick to the golden rule.
Enjoy!
@qujck
8 comments for “Referencing the container is not automatically the Service Locator Anti-pattern”