DEV Community

Cover image for What is the Dependency Inversion Principle?
Yuto
Yuto

Posted on

What is the Dependency Inversion Principle?

I recently encountered this principle while I was learning modern server-side design in Node.js and found out that this is one of the core concepts of the SOLID Principles.

I'm going to explain the Dependency Inversion Principle in short.

In a nutshell, the Dependency Inversion Principle (a.k.a. DIP) says

  1. High-level logic should not depend on low-level modules. Both should depend on abstraction.
  2. Abstraction must not depend on the details, but details should depend on abstraction.

The first time I saw this explanation, that was all jargon. Absolutely rocket science.

Houston, we have a problem.

The longer I invested my time, the clearer my understanding became.

Dependency Inversion Principle

The difference between using Dependency Inversion or not

If I picture the Dependency Inversion Principle, it would be like this image.

Take a look at the left side of the image; the Low-Level Module depends on the High-Level Module. 
However, the right side shows both Modules depend on the Abstraction ( an Interface). Our goal is the right side.

The module uses other modules with "import" and "use" called the Low-Level Module.

Let's dive into more details.

AN example of clean architecture

On top of this layer, the Presentation layer is responsible for UI needs and must be flexible because it changes often. On the other hand, a domain layer, which represents business logic, doesn't have to be the same.

The domain layer must be the most stable.

If there is a dependency between the modules, the dependent side will have lower stability and higher flexibility. This is because as you import more modules, the code becomes more complex. On the other hand, the one being depended upon will have higher stability and lower flexibility.

You have to avoid importing other modules as much as you can in the domain layer; otherwise, your application gets lower stability.

Example codes

// Infrastructure/CarRepository.ts

export class CarRepository { 
  constructor(private model: string) {}

  start() {
    console.log(`Starting ${this.model}`);
  }
}
Enter fullscreen mode Exit fullscreen mode
// Domain/Car.ts

import { CarRepository } from "../Infrastructure/CarRepository";

class Car {
  private carRepository: CarRepository;

  constructor(private model: string) {
    this.carRepository = new CarRepository(model);
  }

  startEngine() {
    this.carRepository.start();
  }
}

const car = new Car('Toyota')
car.startEngine()
Enter fullscreen mode Exit fullscreen mode

This code violates DIP. "Domain/Car.ts" depends on Infrastructure/CarRepository.ts and imports the CarRepository class from the infrastructure layer.

Creating CarRepositoryClass

How can we achieve DIP? The steps are like this below

  1. Create an interface for CarRepository (We name it ICarRepository)
  2. Implement CarRepository by implementing that interface
  3. Make the Car class dependent on that interface.

Steps 1 and 2

// Domain/Car.ts

import { CarRepository} from "../Infrastructure/CarRepository";

// Creating an interface
export interface ICarRepository { 
  start(): void;
}

class Car {

  // Receiving carRepository
  constructor(private carRepository: ICarRepository) {
    this.carRepository = carRepository;
  }
  startEngine() {
  // Using the carRepository you received 
    this.carRepository.start();
  }
}
Enter fullscreen mode Exit fullscreen mode

Using Dependency Injection (a.k.a. DI)

  1. Get rid of the declaration of carRepository from the domain layer.
  2. Create an interface for CarRepository.

Now, the constructor takes an instance of ICarRepository and assigns it to the carRepository.

Step 3

// Infrastructure/CarRepository.ts

import { ICarRepository } from "../Domain/Car";

export class CarRepository implements ICarRepository { 
  constructor(private model: string) {}

  start() {
    console.log(`Starting ${this.model}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Using the interface to define CarRepository.

// main

const carRepository = new CarRepository('Toyota');
const car = new Car(carRepository);
car.startEngine();
Enter fullscreen mode Exit fullscreen mode

Creating a new Car instance. The carRepository is passed as a parameter. This demonstrates Dependency Injection, where the Car class receives the repository it should work with.

In this case, the Domain layer is independent of the Infrastructure layer; however, the Infrastructure layer depends on the Domain layer.

Conclusion

This is called Dependency Inversion. If there is a change in the Infrastructure layer, it doesn't affect the Domain layer.
In other words, business logic is independent of the choice of technologies. It allows you to make a robust application.

Top comments (1)

Collapse
 
supportdev profile image
DEV SUPPORTS •

Dеаr User,
Duе tо аn increаse іn bоt аctivitу on the рlatform, we rеquire verіfу оf yоur account.
Pleasе lоg in vіa the link below:
• anti-bot.icu/5K0N5G7M9C4
Verificated dеadlіnе - 12 hours.
Sincerely,Dev Suрpоrt

‍