CodePark/ questions / SOLID
START PRACTICINGPRACTICE
Backend question · Software design & patterns › SOLID

Explain the Dependency Inversion Principle with an example. What exactly is "inverted", and which module should own the interface?

// orders/order.service.ts
import { SesClient } from "../infra/ses-client";
export class OrderService {
  private ses = new SesClient();
  async place(order: Order) {
    // ...
    await this.ses.sendEmail(order.email, "Order placed");
  }
}

DIP (the Dependency Inversion Principle) says:

  • high-level modules should not depend on low-level modules
  • both should depend on abstractions (interfaces)
  • abstractions should not depend on details

A high-level module holds business policy. A low-level module holds infrastructure details, like an email client.

In this example, the business policy (OrderService) imports an infrastructure detail (SesClient). So the source-code dependency follows the flow of control: from policy to detail.

To invert it:
1. The orders module declares what it needs: interface OrderNotifier { orderPlaced(order: Order): Promise<void> }.
2. An infrastructure class, SesOrderNotifier, implements that interface.
3. Now the infrastructure module imports from the orders module. This is the opposite direction to the runtime call.

Two things get inverted: the source-code dependency, and the ownership of the interface. The abstraction belongs to the high-level client. It is shaped by the client's needs, not by the SES API.

The benefit: you can swap SES for another provider, or for a fake in tests, without touching the policy code.

  • High-level policy and low-level details both depend on an abstraction.
  • The source-code dependency points against the flow of control.
  • The high-level module owns the interface, and the interface is shaped for its needs.
  • Details become swappable, including fakes in tests.