Log in

goodpods headphones icon

To access all our features

Open the Goodpods app
Close icon
Weekly Dev Tips - Dependency Inversion Principle
plus icon
bookmark

Dependency Inversion Principle

09/16/19 • 6 min

Weekly Dev Tips

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.

Dependency Inversion Principle

This week's tip is brought to you by devBetter.com.

Sponsor - devBetter Group Career Coaching for Developers

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.

Show Notes / Transcript

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 ...

plus icon
bookmark

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.

Dependency Inversion Principle

This week's tip is brought to you by devBetter.com.

Sponsor - devBetter Group Career Coaching for Developers

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.

Show Notes / Transcript

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 ...

Previous Episode

undefined - One Step Build Test Run

One Step Build Test Run

Hi and welcome back to Weekly Dev Tips. I’m your host Steve Smith, aka Ardalis.

This is episode 56, on the importance of having a simple way to build, test, and run your application locally.

One Step Build Test Run

This week's tip is brought to you by devBetter.com.

Sponsor - devBetter Group Career Coaching for Developers

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 weekly for 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.

Show Notes / Transcript

I've worked on a lot of projects for a lot of different companies and teams. One thing that dramatically increases the friction of becoming productive on a project is the number of manual and often undocumented steps required to take a new developer on a new machine and get them up and running the application from its source code locally. A lot of the time the developers on the team don't even recognize this as an issue because they've all been there long enough that they've absorbed the knowledge that's been passed down through shared oral history since the ancient times. But new developers, and especially new developers on distributed teams, weren't there last week when someone said "Oh yeah, I changed this thing so now you have to install this tool before you run the app on your machine". They don't just magically know the arcane command line scripts that must be run from 4 different nested subfolders of the application's source code in order to get the system up and running. As soon as you have one new remote team member, it exposes all the implicit knowledge-sharing and manual steps that have taken root in the team's processes and, hopefully, forces the team to make these steps explicit and then to automate them as much as possible.

Simple projects don't require much, if any, documentation or automation. If you have an application that is so simple that any new developer can pull it down from source, use the default compilation step for the platform, like dotnet run for .NET Core or F5 for a Visual Studio-based solution, then you may not need any more documentation or automation than that.

But when it's not that simple, you'll make everybody's life easier if you document and automate the steps. Documentation should be first, since it just makes the steps explicit, and sometimes automation can be difficult to achieve, especially if you need it to work across different operating systems. This is getting easier, though. Once you have the steps documented, you should strive to automate them to the point where common tasks are a single step. Ideally you want a One Step Build Test Run script that does all of these things: builds the app, runs tests against it, runs the app.

But before we get that far, let's talk about how to document the process a bit more. Today, GitHub has become the standard for open source software projects. And GitHub has essentially codified the standard that projects should have README.md files in their root that describe what the project is. Even if you're not hosting your application's code on GitHub, it's a good guess that your dev team has looked at projects on GitHub and is familiar with this convention. Thus, purely from a discoverability standpoint, the best place to put important steps for building and running your app is in its README file in the root of the repo. If you have a lot of repositories that all use the same steps and you don't want that duplication, then put a link to the shared docs into each README.

What about wikis? Wikis are less discoverable. They're not right there in your face when you hit the home page of a repo, and they're not right there with your code when you're looking at the source in your favorite editor like a README file is. You can put more detailed documentation and steps into a wiki if you like, but to make it discoverable you should put links to it in the README file. If you use some CMS system or project management system the answer's the same - use the README as the place to include the links to the relevant information so new team members don't have to try and find it themselves. HTML supports hyperlinks for a reason.

Probably the worst thing you can do in documenting your local build/test/run process is to start by putting it in the README file and then later decide to put the process in a wiki or another location, but not update the README. This will cause team members to use the README and not discover the new location, wasting lots of their time. Bad information is worse than no information. Put a link to the new pro...

Next Episode

undefined - Boundaries with guest James Hickey

Boundaries with guest James Hickey

Hi and welcome back to Weekly Dev Tips. I’m your host Steve Smith, aka Ardalis.

This is episode 58, on the concept of boundaries, with guest James Hickey.

Boundaries

This week's tip is brought to you by devBetter.com.

Sponsor - devBetter Group Career Coaching for Developers

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. Read some of the testimonials on devBetter.com and see if it sounds like it you might be a good fit.

This week we have our first returning guest, James Hickey. James was on the show earlier for episode 48 on how to accelerate your career. This week, he's back to talk about boundaries withing software systems. James is a software developer working remotely in eastern Canada. He's recently written a book about keeping your code clean called "Refactoring TypeScript" (https://leanpub.com/refactoringtypescript). He's also the author of an open-source .NET Core library called Coravel, which provides advanced capabilities to web applications. Welcome back, James!

Boundaries

Hi! I'm James Hickey.

I'm a software developer working remotely in eastern Canada.

When I started my career as a software developer I was thrust into a large codebase for a SAAS that helped automotive manufacturers perform analytics on their financial data.

The way the codebase was organized is probably familiar to most developers - especially those with a background in enterprise-y companies. The solution was organized into 3 main projects: business, DAL (data-access), and "core" (which was just a bunch of classes having no business logic full of public getters and setters).

At the end of the day, all the real business logic was mostly found within stored procedures in the database. So, all those layers didn't serve any real purpose. Business-oriented classes would just call a function from the DAL layer, and that method would call a stored procedure.

As a fresh-out-of-school developer who's trying to learn "how the pros do it", I didn't question this way of organizing code.

Eventually, though, I came to realize that this way of organizing code was terrible. It was hard to find code for specific features. You end up having to switch contexts between multiple projects when working on the same feature.

A Better Way

I've also been in projects having very different ways of organizing code, yet suffered from the same kinds of issues.

Throughout this time, I had a hunch that there was a common issue that was causing these difficulties. It didn't matter how well classes or sub-systems were designed, because, in the grand scheme of things, it was still hard to deal with the codebase as a whole.

As I read books and blogs and listened to well-known industry experts share their knowledge about software design, I came across better techniques and patterns for organizing code and designing software well.

Then, I discovered domain-driven design.

DDD

DDD is a pretty huge subject, but at the heart of the entire philosophy is the idea that the most important thing about managing complexity in software is around putting up boundaries.

In these other systems I've mentioned, the boundaries were enforced the wrong way. Instead of slicing our solutions by technical concerns (like by data-access, objects, interfaces, etc.), DDD teaches us to slice our solutions by business functionality (like shipping, search, billing, etc.)

Since then, I've had the opportunity to learn about other approaches to software design and have formed some opinions around what works well and what generally doesn't work out so well.

Out of all of these ideas, the most important one I've learned and have seen the effects of within real software projects is this idea of creating boundaries.

Different Boundary Types

You might be familiar with the concept called Bounded Contexts. In a nutshell, these are isolated sub-systems or bubbles that you design and build individually. Instead of creating one codebase and shoving all your code into it, you create a codebase or application per specific business feature or area of functionality.

Multiple boundaries can communicate with each other, but not by traditional means. In projects like the ones I mentioned at the beginning, if shipping needed information from the payments feature, it would just reach into the database and query the payments table!

These more strict boundaries mean you can't just reach into another feature's data ...

Episode Comments

Generate a badge

Get a badge for your website that links back to this episode

Select type & size
Open dropdown icon
share badge image

<a href="https://goodpods.com/podcasts/weekly-dev-tips-155124/dependency-inversion-principle-8360081"> <img src="https://storage.googleapis.com/goodpods-images-bucket/badges/generic-badge-1.svg" alt="listen to dependency inversion principle on goodpods" style="width: 225px" /> </a>

Copy