Dependency Inversion Principle – Getting Solid with SOLID – Part 5

Software development has been around for a while. As technology advances so does the need to establish patterns and principles for healthy application development. We know one of those patterns as SOLID. The “D” represents Dependency Inversion Principle (DIP) which is our topic for today.

Today’s post is the final part of a 5-part series discussing SOLID design principles. I’ll intersperse screenshots in the article but full code can be found on GitHub.

What is the Dependency Inversion Principle?

When our favorite uncle — Uncle Bob (Robert C Martin) — first discussed the Dependency Inversion Principle, he defined it with the following:

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend on details. Details should depend on abstractions.

In layman’s terms this really just says that dependencies in our code should be based on abstractions. Generally in .NET this means abstracting our code with interfaces. In other words, method parameters are all against interfaces/abstractions as opposed to concretes themselves.

The reason we know it as dependency inversion is because it inverts the traditional dependency from being “top-to-bottom” to now being “bottom-to-top”.

If you’ve been paying attention to the other SOLID design principles you might be realizing that adhering to DIP will also help you adhere to LSP.

What the Dependency Inversion Principle is not

A lot of people confuse DIP with Dependency Injection (DI; see also my post on DI in .NET Core console applications). It is not the same thing. That’s not to say that they aren’t related necessarily. That said, they are not the same thing. DI is a technique where one object supplies the dependencies of another. It has nothing to do with the hierarchy of dependencies.

Along those same lines, a lot of people confuse Inversion of Control (IoC) with the Dependency Inversion Principle. Similarly, many confuse IoC containers (aka DI container) with DIP. IoC containers are tools/frameworks for managing automatic dependency injection.

Even though both DI and IoC are not DIP, modern software development recommends using them in tandem. I invite you to learn about them and how to use them.

Decomposing DIP – an example

How about we use the default ASP.NET Core API template as our springboard today? When you create a new ASP.NET Core web application in Visual Studio it comes with some basic project scaffolding out of the box. The initial API controller (WeatherForecastController) looks like this:

WeatherForecastController from default ASP.NET Web Application template
WeatherForecastController

Honestly it’s not all that interesting. It does have a dependency on an ILogger<WeatherForecastController>. Said logger does follow the DIP but doesn’t really help illustrate our example today, now does it? Since our default implementation doesn’t do anything perhaps I should do something about it.

To start off I’m creating a WeatherForecastService within the web application. It stores a list of WeatherForecast internally and exposes a GetWeatherForecasts method. There are no abstractions here at all. I’m making use of dependency injection since it is built into the template. Hopefully you’re ok if I gloss over that. Here’s what our new controller and service look like:

Updated WeatherForecastController and new WeatherForecastService
New WeatherForecastController and WeatherForecastService

Ok to be honest this is still extremely boring. That’s the point. All I’m wanting to illustrate here is that the controller has a direct dependency on the WeatherForecastService which has a direct dependency on the WeatherForecast model. Changes in either of those will likely cause a cascade of changes anywhere that references them. The changes shown above are located on the not-inverted branch.

Making it use Dependency Inversion

In order to really show the DIP in action I’m refactoring the code a bit. I’m introducing two new .NET Standard libraries. One of them I’m calling Solid.DependencyInversion.Contracts and the other Solid.DependencyInversion.BusinessLogic. If I were in a real project I’d also have a data access library of some sort which would further illustrate DIP in practice.

Let’s start by looking at the Contracts library. This library defines an IWeatherForecast and an IWeatherForecastService. If I had a data access library I might also define my repositories in here. I’ve seen people separate contracts out by “layer” and I’ve seen them all packaged together. People will argue both ways. Pick one. I’m choosing to have mine all in the same library. These contracts are the high-level dependencies we’re passing around everywhere so they should not have any dependencies of their own.

IWeatherForecastService and IWeatherForecast "model"
IWeatherForecastService and IWeatherForecast “model”

You’ve already seen both the concrete implementation for the WeatherForecastService and WeatherForecast model, but I made some very slight modifications to both. In both cases I now implement the aforementioned interfaces. I also have to make a minor change to WeatherForecastService to return IEnumerable<IWeatherForecast> instead of an enumerable of the concrete model.

Newly modified WeatherForecastService and WeatherForecast model
Newly modified WeatherForecastService and WeatherForecast model

With those changes in mind there is only one more change left to do. We need to modify the WeatherForecastController to have a dependency on the IWeatherForecastService instead of the concrete implementation. That’s pretty simple. Ok I lied, we also need to modify our DI container and add references between the libraries but let’s just pretend that we already did that.

Changes made for this section are found in the added-dip branch.

Graphing the dependencies

I made this ultra-awesome diagram showing the dependency graph and included IWeatherForecastRepository for kicks and giggles. The triangular arrow signifies a consumer relationship whereas the open arrow represents an “implements” relationship. What I hope this illustrates is that the dependencies that cross boundaries, so-to-speak, are the contracts (interfaces) and not the concrete implementations. This is dependency inversion.

Dependency diagram

Our concrete classes depend on a higher-level abstraction to tell them what to do. Concretes care about implementation, nothing else does. I think of it like black-box programming. As a consumer you only care of the inputs and outputs of some external system. How they handle the input and arrive to the output don’t matter to you so long as it is correct.

Decoupling

Another really important point to take away from the Dependency Inversion Principle is that it decouples your code. What does that mean? In its raw form it simply means that since your code depends on abstractions and not concrete implementations, it makes it “easy” for you to switch implementations out.

If you consider my allusion to the IWeatherForecastRepository earlier, perhaps a small discussion there can help solidify what I’m talking about. The idea behind a repository is that its purpose is to provide data from somewhere. The consumer of said repository really doesn’t care where the data comes from, just that it can get it.

With that in mind, my repository might be creating the data in-memory at random (just like I seed into the WeatherForecastService). It might also be connected to a database. Then again, it could just as easily be a client that consumes data from a REST, WCF, or even gRPC service. The point is that our WeatherForecastService really doesn’t care where it gets the data from. It just needs to know the contract.

In that case, then, you can see that we have decoupled our code from the implementation. Our API at the presentation layer can obtain WeatherForecasts from the service without needing to know how it’s done.

Conclusion

Dependency Inversion Principle is all about abstractions. It defines that high-level modules should not depend on low-level modules. Both should depend on abstractions. It also defines that abstractions should not depend on details but should depend on other abstractions. We looked today at how to take a standard Microsoft ASP.NET Core Web Application template and decompose it to adhere to DIP.

Code for today’s post is located here on GitHub.

Additional reading resources

Credits

Photo by Daniel McCullough on Unsplash