Back to Posts

Dependency injection: dealing with large object graph

When using an IoC container to do Dependency Injection, while resolving the composition root seems effortless because the container does it all, in fact it does take CPU time and in some cases it takes more than expected. In this post, I’m discussing about a case in which resolving time became a real problem for me as well as a few methods to solve it ranging from typical to extreme extent.

The problem with large object graph

When I started developing my web application, I used per-web-request lifestyle for most of the services registered to the IoC container. Everything had worked just fine until at one point the object graph reached to more than 600 objects and I had to improve performance for it due to customers’ demand. One of the first thing I noticed was that resolving the composition root alone took from 7ms to 25ms depending on load status of a server. As a side note, size of object graph is not the only factor that causes slow resolve time but it is out of the topic of this post.

Measure

Any performance improvement must start with measuring the current state. However, my experience with this issue taught me one thing: for code that is as critical as resolving composition root of a whole big application, I should have had built-in profiling code for it from the beginning. It can be a custom Performance counter, Mini profiler, or at least using Stopwatch with a switch to turn on/off easily.

Make objects cheap to create and dispose

Resolving a composition root means the whole object graph needs to be created. If some of the objects are expensive to create, namely their constructors take too much time and/or memory to create, I will have a big problem. I had a similar problem before when an object of mine created an object from an 3rd library that took 100ms.

Decompose composition root

Another solution is to decompose the composition root into many smaller roots and only resolve the one I really need for an operation. I do think this should be the most viable options for most applications.

Singleton, singleton, singleton all the ways

When all the above don’t help as in my specific case, an extreme solution could be using Singleton for all the components in the object graph. Don’t get me wrong, I’m not saying it is a great idea or should be used everywhere, but sometimes the end justifies the means.

The point of this solution is that when my composition root is registered as a singleton service to a container, every time the container needs to resolve the composition root it simply returns the same object without having to create the whole object graph again and again. By doing this way, I was actually able to reduce resolving time to less than 0.1ms. However, there are a few challenges to get make this solution viable.

Singleton all the ways

The first challenge is simple: all the objects in object graph that are dependency-injected to other objects must have singleton lifestyle due to the Captive dependency issue

Singleton needs thread-safe

Since those singleton-lifestyle objects can be used by many threads concurrently, they must be thread-safe. Luckily, most of the classes of my application were designed as either a data object or a stateless service class, aka service class that has no data fields but fields that store references to other thread-safe services. Data objects are created manually and passed into the services. As a side note, A minority number of classes do have static fields to store some data though. I just had to make sure they are thread-safe.

But not all classes can be designed that way

It’s true. I have a few options:

  • Use factory pattern which means that I can inject the factory service with singleton lifestyle and use it to create the real objects for each individual request.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface IPerWebRequestLifestyleServiceFactory
{
    public IPerWebRequestLifestyleService Create(/* more parameters can go here */);
}

public class DoSomethingSingletonService
{
    private readonly IPerWebRequestLifestyleServiceFactory perWebRequestLifestyleServiceFactory;
    public DoSomethingSingletonService(IPerWebRequestLifestyleServiceFactory perWebRequestLifestyleServiceFactory)
    {
        // sanity check...
        this.perWebRequestLifestyleServiceFactory = perWebRequestLifestyleServiceFactory;
    }

    public void DoSomething(int someInput)
    {
        var service = perWebRequestLifestyleServiceFactory.Create();
        service.DoSomething(someInput);
    }
}
  • New objects directly.
  • Define a container service which wraps the actual container and inject it to where it is needed. This option is useful when I need create a whole (small) object graph instead of just a single object. While you can rightly point out that this is a kind of service locator anti-pattern, I still happily used it because it worked well for me while none of the other options were viable on that specific situation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DoSomethingSingletonService
{
    private readonly IContainerWrapper container;
    public DoSomethingSingletonService(IContainerWrapper container)
    {
        // sanity check...
        this.container = container;
    }

    public void DoSomething(int someInput)
    {
        var service = container.Resolve<IPerWebRequestLifestyleService>();
        service.DoSomething(someInput);
    }
}

Befriend with container but also beware of it

In summary, an IoC container is a great tool to do DI with so many benefits. The hidden cost of resolving object graph is not container’s fault but is caused by flaws in code design. Such hidden cost is easy to be overlooked because the container magically does all the hard job itself. Of all the methods I mentioned above, the singleton method is pretty extreme and should only be used after all the other methods still don’t get you close to the performance level you need.

Read Next

Dependency injection: dealing with large object graph