Refactoring: TF is Dependency Inversion?

If you’re touchy about language, TF stands for “The Flip” 😋, and when you read this, you’ll see why.

Ikechi Michael
5 min readFeb 10, 2022

In a codebase, everything exists in hierarchies, whether it’s file-system or logical hierarchies. Everything depends on something else, or is depended on, else it becomes dead code. These hierarchies tend to create strong structures that can be resistant to change.

Imagine having to replace the above block. The sheer number of things that could go wrong is terrifying.

Keeping this structure easy to change is where Dependency Inversion comes in. But what is it?

What is it?

There are many explanations of Dependency Inversion e.g. “Higher-level components should not depend on Lower-level components, instead both should depend on abstractions”.

But that creates even more questions. What are high and low-level components? Why is it called “Dependency Inversion”?

I like approaching questions from the “Why” perspective, because I believe that is where meaning can be found. So let’s go … 🚀

Why is it called Dependency Inversion?

Let’s take a look at the words:

Dependency

The opposite of dependent is independent, which means “free”, “unshackled”, “untethered”, so something dependent has to be “bound”, “shackled” or “tethered” to something, right? You’d be correct.

The “bound to something” implies a one-way directionality of dependency. The Child->Parent relationship comes to mind.

Inversion

This means turning something “upside down”, “on its head”, “flipping it 😋".

If we were to flip the Child->Parent relationship, we’d have:

And this is supposed to capture the idea of Dependency Inversion, that we take the traditional direction of dependency between modules, and flip it.

Or does it?

What is the Traditional Direction of Dependency?

We know that code obeys a hierarchy. Things depend on other things.

The following code shows concepts not unusual to see in codebases:

Here, UserProfile is a high-level module for the same reason that JavaScript is a higher-level language than Assembly, because it is closer to the user. The user is not concerned about the UserService where their information is gotten from, but they can interact with the visualUserProfile, making it a high-level module.

So in the traditional direction of dependency, high-level modules like UserProfile, depend on low-level modules, like the UserService , and the UserService in turn, might depend on an Http API accessed via fetch(...) , and this creates a “coupling”, “tethering” or “shackling”.

It is now impossible for the UserService.getCurrentUser method to change without impacting the UserProfile component, because they are bound.

So to keep UserProfile open to change, we need to break its bond to the UserService. How do we do this?

🎺🎺🎺 Dependency Inversion …

We ask the UserProfile, “What do you need to do what you do?”, and it tells us, “I just need to get a name that I can display”.

So we listen to it, and give it just that.

By listening to UserProfile, and giving it exactly what it needs, we can decouple it from UserService .

The way UserProfile tells us what it needs, is an abstraction. That’s what getUsername: () => Promise<string> is, an abstraction of a function that receives a Promise of a string.

So instead of shackling UserProfile to UserService, we shackle UserProfile to its declared abstraction.

That’s a bit like binding one’s self to a religious ideal, instead of a particular religious denomination. For a bit of history, Christianity started out as one church, but began to split when the churches began to deviate from what the members who split, thought was the ideal. So these members weren’t bound to a particular denomination, but to the ideal of what they thought Christianity should be.

So modules should depend on their ideal requirements for themselves or an abstraction, rather than on other modules, and we can only find out what a module’s ideals should be, by “asking it”.

So let’s look at how we can use the UserProfile

So we pass a function () => userService.getCurrentUser().name as the value of getUsername , providing a concrete value for the abstraction.

This way of building, is a bit like building with Legos. Every Lego piece has an interface, by which you can connect other pieces, right? So Lego pieces are not coupled with concrete, unlike brick and mortar buildings, so they are open to change.

The grooves are the interfaces

So now, our codebase’s dependency diagram becomes much flatter. Changing a dependency will affect far fewer modules than before.

One goal of Software Engineering is to keep this number of things that need to change for a change to happen, down as much as possible. Like many things in Software Engineering, it’s a Sisyphean Task in that it gets harder, the more you try to achieve it, but it is a task that must be done.

Sisyphean Task

Conclusion

So we now know that

  • Modules should depend on abstractions
  • Abstractions are defined via an interface, which is defined by types, meaning we need a good type-system to declare abstractions

Where does that leave JavaScript developers, who do not have a type system? They’d better get one 😋.

Meta Conclusion

The term, “Inversion” in “Dependency Inversion”, doesn’t really capture the concept, which is what makes it a little difficult to understand.

We are not flipping the dependency direction, making low-level modules depend on high-level modules, which is what “Inversion” would depict.

Maybe a better term will be “Dependency Decoupling”, or “Dependency Subversion”?

Let me know in the comments.

--

--

Ikechi Michael

I’ve learned I don’t know anything. I've also learned that people will pay for what I know. Maybe that's why they never pay.