Thursday, August 13, 2009

Inject or locate dependencies?

Inversion of Control pattern allows to decouple components (consumers) from their dependencies and takes care of dependencies location and lifetime management through delegation of these responsibilities to external (with respect to dependent type) component. This pattern actively used in composite application development.

Inversion of Control comes in two flavors (Unity provides both capabilities):

Service locator holds references to services and knows how to locate them. It is further used by dependent component to obtain necessary services. In other words consumers play active role.

interface IService
{
    void Do();
}

class ActiveConsumer
{
    private readonly IUnityContainer locator;

    // Reference to service locator comes from outside
    public ActiveConsumer(IUnityContainer serviceLocator)
    {
        locator = serviceLocator;
    }

    public void Do()
    {
        // In order to fulfill its task active consumer relies 
        // on service implementation that is obtained on demand 
        // from service locator
        var service = locator.Resolve<IService>();
        service.Do();
    }
}

Dependency injection makes dependent components passive (little or no work is done to get its dependencies). The only responsibility consumers still care about is to express their dependencies somehow (the way dependencies are expressed depends on pattern implementation, but for this example we will use single constructor automatic injection supported by Unity).

class PassiveConsumer
{
    private readonly IService service;

    // This constructor is used to inject service dependency
    public PassiveConsumer(IService svc)
    {
        service = svc;
    }

    public void Do()
    {
        // We got this dependency from outside and done nothing 
        // to let it happen - so just use it
        service.Do();
    }
}

...

// At this point container resolves consumer's dependency 
// and injects it during construction 
var passiveConsumer = container.Resolve<PassiveConsumer>();
passiveConsumer.Do();

So what is the difference?

First, is dependency from service locator appropriate? If the component in question is supposed to be reused by others you may end up with putting unnecessary constraints (for example you are using some open source service locator but developers that could reuse your component are not allowed to use any open source stuff due to customer’s demand and thus won’t be able to reuse the component).

Second, dependencies visibility. Service locator makes consumer’s “real” dependencies hidden and dependency from service locator itself visible. When dependencies are explicit it is much easier to understand dependent class. Explicit dependencies allows you to assess and control the growth of the component. For example, if your component accepts 10 services in its constructor it may be a sign that it does, or knows or decides too much and it is time to split it. Consider the same thing when using service locator. In order for you to spot number of dependencies you need to look for all unique usage occurrences of service locator. It is not that hard with modern IDE but still it is not that easy as looking at component’s contract.

On the other hand, it makes sense to consider the audience of the component. If it will be reused by others and dependencies are hidden it may require deep knowledge of component’s inner workings in order to use it.

Third, consumer’s relation with dependency. Dependency injection promotes constant relations (from lifetime perspective). Consumer obtains its dependency at construction time and lives with it. On the other hand service locator compasses to temporary relations – get service instance when it is time, call its methods, discard it. Why discard? Because if the component has a constant relation why not use dependency injection otherwise which gives you explicit dependencies?

But anyway, what particular case forces locator usage? When consumer has longer lifetime than its dependency. For example, you are writing smart client application. You organized presentation layer using Model-View-Presenter pattern. Presenter calls remote service in response to user interaction. View controlled by a presenter can be opened for a long time. If presenter gets remote service proxy dependency only once it may happen that proxy will go into faulted state (for example, due to network connectivity problems) and any subsequent calls to it will result in exception. So it is better to dispose proxy every time a particular task accomplished and create new one when new task is on the way or cache it and in response to proxy going faulted create a new one (which is of course harder as long as you need to handle all cases where it used and maintain cache). Thus it seems that service locator is more appropriate in this case.

However we can make short lived dependencies explicit. Let’s assume that IService implementation instance must be disposed every time it is used.

interface IService : IDisposable
{
    void Do();
}

// This is still active consumer as it uses service locator to get service instance
class Consumer
{
    private readonly IUnityContainer locator;

    public ActiveConsumer(IUnityContainer serviceLocator)
    {
        locator = serviceLocator;
    }

    public void Do()
    {
        using (var service = locator.Resolve<IService>())
        {
            service.Do();
        }
    }
}

Service locator has wide surface (it terms of services it can provide) and this makes consumer’s contract opaque. What we need to do is narrow the surface but still provide ability to create service instances (as long as we need to dispose them every time). Abstract factory will do the thing. Factory provides clear connection with service it creates. On the other hand we need to make consumer’s dependency from factory explicit. We will use dependency injection.

interface IServiceFactory
{
    IService CreateService();
}

// Consumer is no longer active as it gets its dependencies from outside
class Consumer
{
    private readonly IServiceFactory factory;

    // The dependency is now explicit
    public PassiveConsumer(IServiceFactory serviceFactory)
    {
        factory = serviceFactory;
    }

    public void Do()
    {
        // We still can create service instances on demand
        using (var service = factory.CreateService())
        {
            service.Do();
        }
    }
}

How about this? That is not all. Factory clearly and explicitly states the relation between dependent component and dependency (service that is created by factory) – it is a temporary relation (as long as it provides ability to create new instances).

Summary:

  • DO make dependencies explicit
  • DO use dependencies to assess and control growth of the dependent component
  • CONSIDER component audience when choosing between dependency injection and service locator
  • CONSIDER using abstract factory pattern and dependency injection to make short lived dependencies (in comparison with dependent component lifetime) explicit

6 comments:

Unknown said...

Hey, just spotted your blog! Nice article!
However, there are several points you outlined I cannot agree with.
While many refer to Inverstion of Control (IoC) as a pattern, I would say that it is a powerful programming technique that helps create a better code. Dependency Injection (DI) Frameworks are simply means of configuration of services and components in your application.
Consider the following examples of classes:

class SlClass {
public SlClass(IServiceLocator locator) {
// something here
}
}

class IocClass{
public IocClass(IAuthenticationService serviceProxy, IRepository repository, ILogger logger) {
// some other code
}
}
So the question is: can you guess what each class does?
On the other hand, you are referring to the problem of using dependency that dies faster than the component that uses it. Right? In case we are using the DI tool, can we delegate it the lifetime and lifecycle management of instances it creates? I’d say yes, we can and what is more important – it is how most DI tools are done.

Dzmitry Huba said...

Hi! Thanks for feedback.

You are right that from looking at class contructor signature you cannot say what the class does. But that is what documentation and/or clear API for.

Explicit dependncies allow you to understand on what this class depends on and not what it does.

May be it is not a good name for the aspect I meant by "short lived dependencies". It is not about lifetime management (and as you correctly pointed DI containers are great in it, like LifeTimeManager concept in Unity). It is about the ability of dependency instance to function properly as long as dependent instance uses it. For example, injected WCF proxy instance hidden behind contract interface can go to fauled state due to network issues and it won't be able to do any remote calls but it can still be alive from memory management point of view.

Unknown said...

A thoughtful analysis, thanks.

So you didn't discuss how the Factory locates the service...

I guess a nice advantage is that we're now free to decide if the Factory uses a Service Locator or has it's dependency injected by a container?

Dzmitry Huba said...

Thanks!

In most cases I expect that factory gets everything it needs to create service through explicit dependencies or it already has right tools. Or otherwise we get into the same problem with factory as we tried to solve for original type.

For example, if we need to create an instance of WCF proxy factory may do it through ChannelFactory and so no dependencies are needed (assuming there is single statically know endpoint configuration).

Or in case there are multiple endpoints factory may request some configuration service as its dependency to decide which endpoint should be used.

Dmitry Ornatsky said...

Great post, and some great answers on SO. We are working for the same company, btw)

Unknown said...

Interessting thoughts! Really high-level programming tips!

I think your arguments are good and I will definitely use your recommendations.

It shows againg, that using patterns is useless, if you don't take the time to think about the pros and cons.

Thanx for sharing.