Friday, October 5, 2012

Service Locator vs Dependency Injection

We recently introduced IoC container (Autofac) in our project. And we liked it, we liked it so much that we have started using in more than a few places now. But soon I realized a trap we are falling into, the trap of "Service Locator" pattern. Before I get into nitty-gritty of this trap, let's briefly talk about service locator vs dependency injection

There are two patterns to implement inversion of control. The code snippet below shows the most common and easy pattern - the service locator

public class CreateCusutomerCommand
{
    private Customer customer;

    public CreateCusutomerCommand(Customer customer)
    {
        this.customer = customer;
    }

    public void Execute()
    {
        var customerPersister = Container.Resolve<IPersistCustomer>();
        customerPersister.Persist(customer);
    }
}

Here we are using a static Container instance to resolve an instance of IPersistCustomer interface. This pattern slightly modified in the code below become a dependency injection pattern.

public class CreateCusutomerCommand
{
    private Customer customer;
private IPersistCustomer customerPersister; public CreateCusutomerCommand(Customer customer, IPersistCustomer customerPersister) { this.customer = customer; this.customerPersister = customerPersister; } public void Execute() { customerPersister.Persist(customer); } }

Here we are not using a container to resolve IPersistContainer, instead, we are accepting an instance through constructor. We are declaring IPersistContainer as a dependency and enforcing it be injected through constructor. So why am I saying that service locator pattern is a trap? There are a few reasons that I have experienced myself


  1. The container API (call to container.Resolve<T>()) is referenced all over the places. Container becomes sticky dependency that is difficult to get rid of. 
  2. If you are doing TDD and you want to write tests for CreateCustomerCommand class in the first example then your tests must have the knowledge of the container and what types are registered in the container. 
  3. Tests may have a need to change the behaviour offered by the registered types in the container. 
  4. The only way to do that would be create proxy types and register them again from the tests. 
  5. One of the above would lead to your test project being dependent on container assemblies
If you use dependency injection pattern and wire it up properly (e.g. If I'm building an MVC project and using Autofac, I would use Autofac MVC integration and delegate the controller creation to autofac) then you would rarely need to resolve a type yourself manually. The container API calls are not littered in you code base. 

The most interesting part comes when you are writing tests. It's very easy to write tests against class in the second example. I know that I need to pass in some implementation of IPersistCustomer interface. I can just mock it and there is no other setup required. 
Life has become so easy with this change.

No comments:

Post a Comment