Search code examples
c#restdependency-injection.net-coreservice-locator

Service Locator Anti-Pattern Okay For Factory Registration


I'm creating a REST framework, and I'm wondering if it's okay to provide access to the service provider since it will be used in the Factory. I have a generic service which has events that fire on Create and Update

public event Func<object, TDTO, Task> CreateEvent;

protected virtual async Task OnCreated(TDTO dto)
{
    await ServiceEventHandlerRule.OnServiceEvent(CreateEvent, this, dto);
}

public async Task<TDTO> Create(TDTO dto)
{
    var entity = this.DtoToEntity(dto, new TEntity());
    this._context.Set<TEntity>().Add(entity);

    dto = await this.SaveEntity(entity);
    await OnCreated(dto);
    return dto;
}

I'm registering Events on my sevices in a factory, Currently I'm exposing a Func<IServiceProvider, Object, TDto> Event Registration looks like so, this occurs in my startup.cs:

var serviceConfig = new ServiceConfiuration<User, UserDTO>();
serviceConfig.EventMapping.AddCreateEvent(async (sp, obj, dto) => {
    var hubTaskQueue = sp.GetService<IHubServiceTaskQueue>();
    hubTaskQueue.QueueCreate<THub, TDTO>(dto);
});
services.AddScopedRestService(serviceConfig);

Users will need access to a few extra dependencies for their events so I've given them access to the IServiceProvider,

What is the downside of using the Service Location Pattern During Registration?

Should I mask IServiceProvider and make a few Generic Overload which will auto resolve their dependencies?


Solution

  • In Dependency Injection Principles, Practices, and Patterns, we define Service Locator as:

    A Service Locator supplies application components outside the Composition Root with access to an unbounded set of Volatile Dependencies. [Chapter 5.2]

    A Composition Root is a single, logical location in an application where modules are composed together. You can find a detailed description of what a Composition Root is in this excerpt from the book.

    In respect to your question, the important part of the Service Locator definition is: "outside the Composition Root". In other words, accessing an unbounded set of Volatile Dependencies from within the Composition Root is fine, and is not an implementation of the Service Locator anti-pattern. Accessing them outside the Composition Root, however, is an anti-pattern.

    So to answer your question, you can safely let your factory depend on the container or an abstraction that resolves an unbounded set of dependencies, as long as your factory implementation is part of the Composition Root.

    You can achieve this by placing the factory's abstraction close to your application logic, while you create a factory implementation in the start-up assembly of your application.

    Users will need access to a few extra dependencies for their events so I've given them access to the IServiceProvider, What is the downside of using the Service Location Pattern During Registration?

    If the code that depends on the IServiceProvider is not part of the Composition Root, they use the Service Locator anti-pattern, and this should be prevented. Main problems with the Service Locator anti-pattern are:

    • The module drags along the Service Locator as a redundant Dependency.
    • The module makes it non-obvious what its Dependencies are. This makes such module harder to test, obscures the fact that a class might be too complex, and makes it harder to verify the correctness of the dependency graph, since dependencies are fetched lazily.

    Instead, you should let those users define their dependencies as constructor arguments.