The Dependency Inversion principle is one of the SOLID principles of OOP and is very useful for reducing tight coupling between modules in C# and Java applications.
Hi and welcome back to Weekly Dev Tips. I’m your host Steve Smith, aka Ardalis.
This is episode 57, on the Dependency Inversion principle.
This week's tip is brought to you by devBetter.com.
Need to level up your career? Looking for a mentor or a the support of some motivated, tech-savvy peers? devBetter is a group coaching program I started last year. We meet for weekly group Q&A sessions and have an ongoing private Slack channel the rest of the week. I offer advice, networking opportunities, coding exercises, marketing and branding tips, and occasional assignments to help members improve. Interested? Check it out at devBetter.com.
Ok, now we've reached the last and in my opinion the most important of the SOLID principles, D for Dependency Inversion. The Dependency Inversion Principle, or DIP for short, has a longer definition that most of the other principles and is often conflated with the related coding technique, dependency inversion, or DI. The principle states that High-level modules should not depend on low-level modules. Both should depend on abstractions (interfaces or abstract types). and further, Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.
Let's look quickly at each of these two parts. The first part talks about high level and low level modules. The "level" of a module has to do with how near or far it is from some kind of I/O device. That could be the user interface or it could be a local file or a database server. Low level modules deal directly with these kinds of I/O devices or destinations. High level modules do not know about or deal with specific kinds of I/O. These are things like business logic classes and behavior that model how a system works. In many systems that don't use abstractions, high level modules depend on low level modules, or the high level logic is mixed in with low level concerns in the same modules. Both of these approaches violate the Dependency Inversion Principle. Instead, these modules should communicate with one another using abstractions like C# or Java interfaces. Both kinds of modules would depend on a common interface, typically with the low level module implementing the interface and the high level module calling it.
The second part suggests that abstractions - interfaces typically - should not depend on details. So an example of this would be if you had an interface for fetching information about a customer. One approach would be to write the interface so that it returned a
SqlDataReader as its return type, where the data reader had the customer info. This exposes the details of how the data is stored, since you would only use a
SqlDataReader to fetch the data from a SQL database. One benefit of following the Dependency Inversion principle is modularity. You could change that interface to return a simple
List type and that
List could come from any number of storage locations, from databases, to files to in-memory stores or web APIs. So, that covers how abstractions should not depend on details - what about the last bit that says details should depend on abstractions? That's talking about your low-level modules that actually communicate with I/O. These should depend on your interfaces by implementing them.
If you're build a system composed of multiple projects it can be extremely difficult to follow the Dependency Inversion principle if you don't structure your project dependencies appropriately. This means ensuring that your abstractions - your interfaces - live in a project alongside your business model entities and that your implementation details live in another project that references this one. I have a GitHub repository and solution template called Clean Architecture that you can use as a starting point for new ASP.NET Core applications that need to follow SOLID principles and use clean architecture. You'll find a link to it in the show notes or just google ardalis clean architecture.
A key benefit of Clean Architecture that is enabled by following the Dependency Inversion Principle is that your business model has no dependencies on external infrastructure concerns. These dependencies are a huge part of why legacy codebases are often difficult or impossible to write unit tests for. By keeping these dependencies separate and in their own project that other projects do not depend upon, it makes it much easier to unit test the most important part of your application: its business domain model. I talk more about this in my DDD Fundamentals course with Julie Lerman on Pluralsight if you want to see this in action. You can also check out the eShopOnWeb reference application that I built for Microsoft and its companion book, Architecting Modern Web Applications with ASP.NET Core and Microsoft Azure.
That’s it for this week. If you want to hear more from me, go to ardalis.com/tips to sign up for a free tip in your inbox every Wednesday. I'm also streaming programming topics on twitch.tv/ardalis most Fridays at noon Eastern Time. Thank you for subscribing to Weekly Dev Tips, and I'll see you next week with another great developer tip.