C# – Help me resolve this circular dependency

cdependency-injectiondomain-driven-designlayers

This would be behind a spoiler tag if I could get the markdown to work. Sorry.


As soon as I typed in "circular dependency", I got a ton of suggested results. Such as:

I've read through those, and others. I still don't know how to solve my problem, thus this question:


Ok, real question.

The "typical" method of dependency injection for a data access layer goes something like this:

  1. Create an interface for your persistence layer within your domain layer.
  2. Have your domain services accept that interface in their constructors.
  3. Make sure your persistence layer implements that interface. This implies that the persistence layer depends on the domain layer, and all is good.
  4. Your client layer has references to the domain layer and to the persistence layer.
  5. Your client layer instantiates the persistence object and injects it into the domain services. This is typically done with a using clause.

For example:

// Simplified...
using (var p = new PersistenceThing ()) {
    var s = new ServiceThing (p);
}

… and if you have multiple layers…

using (var p = new PersistenceThing ()) {
    using (var s1 = new LowerLayerThing (p)) {
        using (var s2 = new HigherLayerThing (s1)) {
            // How deep does the rabbit hole go before you can actually do anything?
        }
    }
}

I have a valid need to move the creation of these service objects into factories. (The reasons are long, involving separation into separate tiers). That changes my project layout a little.

Going from the top down:

  • I have a client project.
  • Below that, I have an "upper layer interface" project that contains the service contract for the layer and the factory for the layer. The client project need only reference this "upper layer interface" project.
  • Next, I have an "upper layer implementation" layer. This layer references the "upper layer interface" project and the "lower layer interface" project.
  • Then there's a "lower layer interface" project containing the service contract and factory. It doesn't reference anything.
  • Then a "lower layer implementation" layer. It references the "lower layer interface" project and… and…

And then I get to the persistence layer and it all breaks down.

I need to have a factory to handle the instantiation of my persistence layer.
The lower service layer needs to have or reference the persistence interface because that's what it's working against. The factory needs to have or reference that same interface so that it can expose the proper method signature. Easy enough: the "persistence interface" project references the "lower service" project. But the "lower service" project needs to have access to the factory so that it can get an instantiation of the persistence layer.

My first thought was to move the interface into the "persistence interface" project, but it still needs a reference to the "lower service" project because it references all of the domain object types. My second thought was to move the factory into the "lower service" project, but it needs references to the "persistence implementation" layer so that it has something to instantiate. But the "persistence implementation" layer would still need a reference to the "lower domain" layer so that I can implement the interface.

Best Answer

That's not how layered (or tiered) architecture works. Specifically: each layer only has knowledge of the layers directly below it and any required shared objects. Which contradicts items 4 and 5 in your list of how typical dependency injection works. Which is why you have a circular dependency problem.

In a three layer system consisting of client, domain and persistence layers: 1. The client layers "knows" about the domain layer through interfaces and shared data objects 2. The domain layer "knows" about the client layer interfaces (because it implements them) and the shared data objects the client requires 3. The domain layer also "knows" about the persistence layer interfaces and persistence shared data objects. 4. The persistence layer "knows" about the persistence layer interfaces (because it implements them) and persistence shared data objects

So you end up with five projects. A client project, a domain project, a persistence project, a project that is referenced by the client and the domain and a project that is referenced by domain and persistence. If you have a common data object that you want to reference in all layers, then that goes in another project.

Assuming this is a desktop application you will have one additional parent project that references everything but is only there to start up the app. In that project you can use something like Unity to do your actual injection.