← Home
🏗Crack System Design with DSA AnimatorSystem DesignNEW
🏗 System Design›SOLID Principles

⬇️Dependency Inversion Principle (DIP)

Depend on abstractions, not on concretions — and own the abstractions from the high-level policy.

📌 Key IdeaHigh-level business rules should not depend on low-level details (SQL, HTTP, filesystem). Both should depend on abstractions (interfaces) that you define in the domain layer.
✨

Why It Matters

  • Testability — Swap MySqlOrderRepository with InMemoryOrderRepository in unit tests — no DB.
  • Flexibility — Migrate stores without rewriting OrderService.
  • Stable domain — Business rules compile against your interfaces, not vendor SDK types everywhere.
💡

In Layman's Terms

You don't plug your laptop straight into the power plant — you use a standard socket and the plant side uses the same standard. DIP is that socket: policy (what you want done) talks to an interface; detail (how MySQL stores rows) plugs in behind it.

"Inversion" means the old diagram had arrows down from app → database; now arrows point inward to the interface — the database module implements what the domain declared.

🌍

Real-World Analogy

🔌 Power adapter

Your phone cares about 5V USB, not which coal plant generated the electrons. The abstraction is the USB contract; the grid is an interchangeable implementation.

Similarly OrderService cares about persisting orders, not whether rows live in PostgreSQL or a CSV file.

🖼

Visual

❌ Depends on concrete details
OrderService talks straight to MySQL — hard to test, hard to swap.
  class OrderService {
      private final MySqlOrderRepository repo =
          new MySqlOrderRepository();   // concrete — hard to test, hard to swap
  }
✅ Depend on abstraction
Domain defines `OrderRepository`; MySQL and in-memory impls plug in — dependency direction inverts.
  interface OrderRepository { void save(Order o); }

  class OrderService {
      private final OrderRepository repo;
      OrderService(OrderRepository repo) { this.repo = repo; }
  }

  class MySqlOrderRepository implements OrderRepository { ... }
  class InMemoryOrderRepository implements OrderRepository { ... }  // tests

DIP flips the dependency: domain owns OrderRepository; MySQL is one adapter.

✅

When to Use

  • High-level modules would otherwise `new ConcreteThing()` deep inside.
  • You need multiple implementations (prod DB, mock, alternate vendor).
  • Building hexagonal / clean architecture — ports and adapters.
  • Framework code should not leak JDBC types into domain entities.
🚫

When NOT to Use

  • Primitive utilities (String, List) — over-abstracting hurts.
  • Single implementation forever with zero test need — YAGNI until the second implementation or testing pain appears.
  • Cross-cutting logging — sometimes a thin facade is enough without a full port hierarchy.
🚨

Anti-Pattern (What NOT to Do)

Service hard-wires MySQLBadjava
public final class OrderService {

    public void place(Order order) {
        try (Connection c = DriverManager.getConnection(MYSQL_URL)) {
            PreparedStatement ps = c.prepareStatement(
                "INSERT INTO orders (...) VALUES (...)");
            ps.setString(1, order.id().toString());
            ps.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
Domain is married to JDBC URL, SQL shape, and driver — swap DB or test without DB = rewrite.
✅

The Right Way

Domain depends on port; adapter implements itGoodjava
public interface OrderRepository {
    void save(Order order);
}

public final class OrderService {
    private final OrderRepository orders;

    public OrderService(OrderRepository orders) {
        this.orders = orders;
    }

    public void place(Order order) {
        orders.save(order);
    }
}

public final class MySqlOrderRepository implements OrderRepository {
    public void save(Order order) { /* JDBC here — isolated */ }
}
OrderService is two lines of persistence orchestration; storage detail is one class you can mock or replace.
⚖️

Pros & Cons

👍 Pros
  • Aligns with IoC and Dependency Injection (constructor injection).
  • Domain stays framework-agnostic — adapters sit at the edge.
  • Enables parallel development: API-first interfaces, mock implementations.
👎 Cons
  • More interfaces and wiring — Spring / Guice help but add learning curve.
  • Wrong abstractions leak — if OrderRepository mirrors SQL too closely, you only moved the problem.
📊

Direct dependency vs inverted dependency

AspectApp → ConcreteApp → Interface ← Concrete
Compile-time couplingTo MySQL driver / SQLTo your OrderRepository only
Unit testEmbedded DB or heavy mocksIn-memory fake
Swap datastoreEdit OrderServiceNew adapter class only
Who owns the type?Infrastructure owns APIDomain owns port
🔀

Common Confusions Cleared Up

DIP vs Dependency Injection

DIP is the *design rule* (depend on abstractions). Dependency Injection is a *mechanism* (constructor, setter, framework) that supplies those implementations. You can do DIP without a DI container — plain constructor parameters.

DIP vs Inversion of Control

IoC means control flow is inverted — the framework calls your code. DIP is specifically about dependency direction toward abstractions. Spring uses IoC to achieve DIP-style wiring.

⚠️

Common Mistakes / Anti-Patterns

  • new RestTemplate() / new HttpClient() scattered in domain services.
  • Service locator hiding getBean() — still global, harder to test than explicit injection.
  • Interfaces that mirror one concrete class 1:1 with no behavioural abstraction.
🎤

Interview Q&A

🔮

Likely Follow-up Questions

  • Sketch ports and adapters for a payment flow.
  • When does interface owned by infrastructure still satisfy DIP?
  • How do Spring `@Bean` and @Autowired support DIP?
🧾

Quick Cheat Sheet

RuleHigh-level + low-level both depend on your abstractions.
Smellnew ConcreteDependency() inside domain logic.
FixConstructor inject interfaces · adapters at edge.
Not DIDIP is the principle; DI is how you wire implementations.
🔗

Related Topics