Circular Dependencies in Dependency Injection
By: Brian Mearns
Tags: Software Development, Guice, Dependency Inversion, Circular Dependencies, Dependency Injection

Another option is not to eliminate, but to reverse one of the dependencies. This is the option we ultimately used to break my circle.

But in order to get there, the first step was to identify the entire circle of dependencies. From there, we started to look at which of these dependencies were most important, which made the most sense. Very often, we end up with dependencies that aren’t actually necessary and aren’t even conceptually appropriate to our system. Any such tenuous links in the dependency chain should be in your first set of candidates for breaking.

The links in the chain that jumped out as most suspect to us were:

Rule 5 — MessageRouter needs a HeartbeatActor

needs a Rule 2 — Shutdownable is implemented by BatchManager

is implemented by Rule 1 — HeartbeatActor needs a Shutdownable

Rule 5 jumped out because in the actor system the MessageRouter was sending messages directly to the HeartbeatActor , that's why it had the dependency on it. It seemed plausible that the MessageRouter didn’t actually need the HeartbeatActor directly, it might be able to use an indirect reference to the actor, which the actor system could then resolve just-in-time. In this case, the HeartbeatActor wouldn’t need to exist in order to create the MessageRouter , it would just need to exist by the time the MessageRouter was ready to send it messages. In this way, we wouldn’t actually be breaking the dependency between the two classes, but we would be delaying it to the point that Guice no longer cares about it.

Rule 2 jumped out for two reasons: first of all, because it was one of the ones we had added, and second of all because it was the only link in the chain where the dependency was defined by an “is-a” relationship rather than a “has-a”. If we could change that is-a into a has-a, we could reverse the direction of the dependency and therefore break the circle.

Rule 1 jumped out because the HeartbeatActor is an actor, so it seemed like maybe it should be controlling shutdown by sending a message, instead of calling an interface method. In all likelihood, this would not have solved the problem, it would have just added more links in the cycle. Most likely we would simply have added something like a ShutdownActor which would have become dependent on the Shutdownable , and the HeartbeatActor would in turn just become dependent on the ShutdownActor .

However, this one jumped out even more because we knew about Rule 2: we knew that the Shutdownable that would be passed into the HeartbeatActor actually was the BatchManager , and it seemed like it was giving the HeartbeatActor too much power to have that kind of access to what is really the application class. But that really was more a problem with Rule 2 again, not so much Rule 1.

With the dependency between BatchManager and Shutdownable reversed as shown here, the cycle is broken.

So we decided to go with breaking Rule 2. Instead of having the BatchManager implement Shutdownable (so that in order to satisfy a dependency on Shutdownable , you would ultimately be dependent on the BatchManager ), we would implement the Shutdownable interface in a dedicated object that could then be passed into the BatchManager ’s constructor. This makes BatchManager dependent on the Shutdownable , instead of the other way around.

By reversing this arrow, creating the Shutdownable can now be the first thing Guice does. Once it has that, it can create the HeartbeatActor , then the MessageRouter that depends on it, and so on, until it finally has everything it needs (including the Shutdownable ) to create the BatchManager .

The Solution

To implement the solution, we created a ShutdownManager class, a simple stand-alone class with no dependencies. All it really does is keep a shutting down flag which is initially false and can be set to true at most once.

In order to satisfy the dependency, it needs to supply a Shutdownable interface, which has a single method: void requestShutdown() . However, a separate but related dependency that Guice needed to manage was a ShutdownFlag (it wasn't part of the circular dependency), which also has a single method: boolean isShuttingdown() ; this is used to query the shutdown state. Clearly, if we were going to be moving the shutdown management out of the BatchManager into this new class, it would need to provide for this interface as well.

Lastly, we would need a way for the BatchManager to actually control the shutdown, specifically to say “shutdown is starting” and “shutdown is complete”, so the new ShutdownManager class would need to supply this interface as well, and the BatchManager would be dependent on it.

However, as mentioned above, we didn’t want to just be passing around this BatchManager instance everywhere, because it was too powerful. It provided an interface to actually shut down the system when all that was ever really needed outside of the BatchManager was the ability to request a shutdown.

The dependencies that this ShutdownManager was going to be satisfying weren’t typed as ShutdownManager , they were typed as Shutdownable and ShutdownFlag . But we still didn’t feel good about passing the actual ShutdownManager instance around to all of these other modules; they could always do an instanceof check and cast it to a ShutdownManager and leverage those more powerful interfaces.