Skip to main content
Clean Architecture 80 XP · 8 min

DIP: The Dependency Inversion Principle

Source code dependencies should point only to abstractions — Abstract Factories create the architectural boundary between volatile concrete code and stable policy.

Showing
Ad (728×90)

Why this matters

DIP states that high-level policy (business rules) should not depend on low-level detail (databases, frameworks, I/O). Both should depend on abstractions. In the ideal DIP world, every source-code dependency points toward an abstract interface, never toward a concrete implementation.

Martin illustrates the practical consequence with the Abstract Factory pattern. Business code needs to create objects — an order, a repository, a payment — but it must not directly call new MySQLOrderRepository(), because that would couple stable business policy to a volatile infrastructure choice. The Abstract Factory creates concrete objects on the far side of an architectural boundary, so business code depends only on the factory interface and the repository interface. The "curved line" separating abstract from concrete is the architectural boundary itself — everything stable and reusable on one side, everything volatile and replaceable on the other.

The problem

OrderService directly instantiates MySQLOrderRepository — the business layer is hardwired to a database technology it should never know about.

Bad

class MySQLOrderRepository:
    def save(self, order: dict) -> None:
        mysql.execute("INSERT INTO orders ...", order)

class OrderService:
    def __init__(self):
        self.repo = MySQLOrderRepository()   # hardwired to MySQL!

    def place_order(self, order: dict) -> None:
        # business logic...
        self.repo.save(order)
class MySQLOrderRepository {
  save(order: Order): void {
    mysql.execute("INSERT INTO orders ...", order);
  }
}

class OrderService {
  private repo = new MySQLOrderRepository(); // hardwired — DIP violation

  placeOrder(order: Order): void {
    // business logic...
    this.repo.save(order);
  }
}

The solution

OrderService depends only on an abstract repository interface. An Abstract Factory creates the concrete implementation — business policy and infrastructure detail are separated by an architectural boundary.

Good

from abc import ABC, abstractmethod

class OrderRepositoryInterface(ABC):
    @abstractmethod
    def save(self, order: dict) -> None: ...

class OrderRepositoryFactory(ABC):
    @abstractmethod
    def create(self) -> OrderRepositoryInterface: ...

# ── Stable policy (business side) ────────────────────────────────────────────
class OrderService:
    def __init__(self, factory: OrderRepositoryFactory):
        self.repo = factory.create()   # depends on abstraction only

    def place_order(self, order: dict) -> None:
        # business logic...
        self.repo.save(order)

# ── Volatile detail (infrastructure side) ────────────────────────────────────
class MySQLOrderRepository(OrderRepositoryInterface):
    def save(self, order: dict) -> None:
        mysql.execute("INSERT INTO orders ...", order)

class MySQLRepositoryFactory(OrderRepositoryFactory):
    def create(self) -> OrderRepositoryInterface:
        return MySQLOrderRepository()
interface OrderRepository {
  save(order: Order): void;
}

interface OrderRepositoryFactory {
  create(): OrderRepository;
}

// ── Stable policy (business side) ────────────────────────────────────────────
class OrderService {
  private repo: OrderRepository;

  constructor(factory: OrderRepositoryFactory) {
    this.repo = factory.create();  // depends on abstraction
  }

  placeOrder(order: Order): void {
    // business logic...
    this.repo.save(order);
  }
}

// ── Volatile detail (infrastructure side) ────────────────────────────────────
class MySQLOrderRepository implements OrderRepository {
  save(order: Order): void {
    mysql.execute("INSERT INTO orders ...", order);
  }
}

class MySQLRepositoryFactory implements OrderRepositoryFactory {
  create(): OrderRepository { return new MySQLOrderRepository(); }
}

Key takeaway

Every new ConcreteClass() inside your business logic is a DIP violation — it hardwires stable policy to a volatile detail. Use a factory or inject the dependency so the business layer never knows what concrete type it's working with.

Done with this lesson?

Mark it complete to earn XP and track your progress.