Search code examples
c#dependency-injectionn-tier-architecture

How to divide the dependencies binding to a DI container between tiers and layers?


I'm developing a microservice application divided into 4 layers (API, Application, Domain and Infrastructure).

The composition root of my application is an assembly in the API layer. From what I understand all the binding and registering of dependencies in an application using a DI framework is typically done in the composition root (aka the API layer).

But doesn't this assumption about the composition root indicate it knows too much about the application? Dependencies to the Infrastructure layer will be set at the API layer. Where is the encapsulation there?

However if each layer could register its own internal dependencies, then wouldn't it couple a specific DI framework to the whole application? I would also need to pass the container between the layers so each layer can register its stuff.

How can this situation be managed?


Solution

  • But doesn't this assumption about the composition root indicate it knows too much about the application? Dependencies to the Infrastructure layer will be set at the API layer. Where is the encapsulation there?

    There will be no encapsulation in your Composition Root. The Composition Root is strongly coupled to all assemblies, modules, and components in your application. But this isn't particular to the use of Loose Coupling or Dependency Injection. Due to the way how library dependencies work—they are transitive—means the startup path of your application will always (directly or indirectly) depend on all other assemblies in your application. Because of this, the start-up path should be the most volatile part of the application (and when you follow the Composition Root pattern, it will be). It should follow the Stable-Dependency Principle; one of Principles of Component Design (see chapter 28 of Agile Principles, Patterns, and Practices for a detailed discussion).

    it knows too much about the application?

    I would say it knows just enough. It's job is object composition, which means it must know about the structure of all components in the system. It shouldn't know all implementation details, but just enough to be able to correctly compose the system. You can try to move composition back to individual modules, but that leads to the situation where knowledge of composition gets scattered out through the application, while your entry point still depends on everything, because of the transitive nature of assembly dependencies. You'll end up in a worse place. Centralization of object composition is the whole idea of the Composition Root.

    In section 4.1 of DIPP&P, Mark Seemann and I talk more extensively about the Composition Root and the transitive nature of library dependencies.