The Composition Root

Build your program before turning it on.

Our components have dependencies on the delegates they use.

We’ve explored some of the benefits of our components relinquishing control over where those dependencies come from.

But where do those dependencies come from?

Foo and Bar

Say you have a component Foo which delegates some of its responsibility to another component Bar:

class Foo {
 doThing() { this.bar() }
}

With inversion of control in mind, we know that Foo should avoid the temptation to obtain an instance of Bar for itself:

class Foo {
    constructor() {
        this.bar = new Bar()
    }
}

Instead, Foo should take in Bar (though a constructor parameter perhaps):

class Foo {
    constructor(private bar: Bar) { }

    doThing() { 
        this.bar() 
    }
}

But who is actually responsible for creating the instance of Bar that gets passed to or “injected” into Foo?

Intuition might tell us: “whoever is responsible for creating Foo”.

Infinite Regression?

It’s a sensible thought that, whoever creates Foo (let’s say Quz ) would need to have an instance of Bar on hand in order to call Foo ‘s constructor.

But wait a minute…

Quz creating a Foo would be violating the very principle we’re trying to apply.

Quz depends on Foo , yet shouldn’t be engaged in directly constructing it. Instead, Quz ought to invert control and take in its dependencies via inject too.

But then who creates it, or its dependencies?

class Quz {
    constructor(private foo: Foo) { }

    fooIt() { 
        this.foo.doThing() 
    }
}

Finite Regression!

If our principle regarding dependencies is “someone else should provide them” and we apply that principle consistently, then no matter where we look, the responsible party is “someone else”!

It’s natural to wonder: Is anyone actually responsible for constructing instances?

Luckily our programs have a base-case, or natural stopping point for this seemingly infinite regression of inversion of control. If we keep pushing responsibility upwards (downwards?) we eventually arrive at the entry-point of our program! At this point there is nothing and nowhere to invert control to.

We must finally buckle down and start creating instances.

The composition root (the program’s entry-point or near it), is where the construction of all the dependencies that make up your program’s object-graph takes place.

Program Topology

Say our program had the following dependency chain:

App -> Foo -> Bar -> Baz
                \ -> Boz

Notice that there is an inherent ordering.

App cannot be constructed until Foo is.

Foo cannot be constructed until Bar is.

Bar cannot be constructed until Baz and Boz are.

graph BT;
 App --> Foo;
 Foo --> Bar;
 Bar --> Baz;
 Bar --> Boz;

Therefore in the authoring of the composition root we must work out this “topological order” in which we’ll do our constructions:

const baz = new Baz()
const boz = new Boz()
const bar = new Bar(baz, boz)
const foo = new Foo(bar)
const app = new App(foo)

app.start()

An Unexpected Separation of Concerns

While the Composition Root may seem a bit strange, and it certainly is, it does have one key thing to observe about it:

💡 The Composition Root is effectively a separation of two general
concerns in your program: Business Logic and Object Composition

Typically the concern of Object Composition is spread-out throughout our whole program, intimately intermingling with our business logic. However, through consistent inversion of control, it all bubbles up to the top creating a natural separation.

Summary

In this chapter we explored:

  • The consequences of consistent application of inversion of control through dependency injection.
  • The way our program’s entry-point prevents inversion of control going on forever.
  • The nature of the so-called “composition root”.
  • The topological challenge we face implementing the composition root.

While modern IDEs make figuring out the right construction ordering relatively straight-forward, the composition root is a fairly strange and awkward result of everything we’ve learned so far.

Next Chapter

🤖 CH5: Automatic Dependency Injection

In the next chapter, we’ll see how to use the computer to automate the composition root.