Question 8 · Section 18

What is Dependency Inversion Principle?

The Dependency Inversion Principle (DIP) is the foundation of flexible architecture. It states:

Language versions: English Russian Ukrainian

Deep Dive (Under the Hood)

The Dependency Inversion Principle (DIP) is the foundation of flexible architecture. It states:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
  2. Abstractions should not depend on details. Details should depend on abstractions.

Explanation: Abstraction = interface/contract (WHAT to do). Detail = concrete implementation (HOW to do it). Repository interface — abstraction, PostgresRepository — detail. Business logic depends on Repository, and PostgresRepository depends on Repository.


Senior Insight: What Exactly is “Inversion”?

In classic procedural programming, dependencies look like this: Business Logic -> DB Access -> Disk Driver. Business logic is on top, but it’s rigidly tied to a specific database.

Inversion means we reverse the dependency arrow: Business Logic -> [DB Interface] <- DB Implementation. Now business logic owns the interface, and the DB adapts to it. We can replace the DB without changing a single line of business code.


DIP vs Dependency Injection (DI) vs IoC

These concepts are often confused:

  • DIP: Architectural principle (concept).
  • IoC (Inversion of Control) — general principle: YOU don’t control the flow, the framework does. Example: in Spring you don’t create new Service(), Spring creates them for you.
  • DI (Dependency Injection) — mechanism of how Spring passes dependencies (via constructor, field, setter).
  • DIP (Dependency Inversion) — architectural rule: WHO to depend on (abstractions, not details).

Implementation in Spring Framework

Spring is a powerful IoC container implementing DIP through DI.

  • Under the hood: Spring scans classes, creates a dependency graph, and injects (Inject) implementations into interfaces.
  • Senior Tip: Always inject dependencies via constructor. This guarantees field immutability and allows easy Unit testing without starting the Spring context.

Performance and Highload

  • Startup Latency: Large number of abstractions and dynamic binding at runtime (via proxies or reflection) slows application startup.
  • GraalVM & AOT: Modern technologies (Ahead-of-Time compilation) allow “resolving” all DIP dependencies at build time, turning them into direct method calls, which removes runtime overhead and makes the application start instantly.

GraalVM Native Image is not compatible with all libraries. Reflection, dynamic proxies, and JNI require special configuration (reflection-config.json).


Edge Cases

  • Circular Dependencies: The most common bug when implementing DIP. Service A depends on interface B, and implementation B depends on interface A.
    • Solution: Refactoring, extracting a third component, or using @Lazy (but this is a “workaround”).
  • Abstractions Overkill: No need to create an interface for every class. If a class will have only one implementation throughout the project’s life — an interface may be unnecessary complexity (YAGNI violation).

Diagnostics

  • Unit Testing: If you can test business logic by substituting a Mock instead of a real DB in 5 minutes — your DIP is fine.
  • Component Scanning: Make sure abstraction levels don’t overlap (e.g., domain model should not depend on JPA annotations if you want true DIP).

When DIP is NOT Needed

  1. Simple utilities/scripts — depending on java.util.List doesn’t require abstraction
  2. Stable dependenciesjava.lang.String won’t change, abstraction is redundant
  3. Prototypes — speed is more important than architecture

Summary for Senior

  • DIP makes the system testable and extensible.
  • Dependencies should go toward more stable modules (interfaces).
  • Interface should be designed for the consumer’s needs, not repeat the implementation’s methods.
  • Use Constructor Injection as the de facto standard.

🎯 Interview Cheat Sheet

Must know:

  • DIP: high-level modules don’t depend on low-level — both depend on abstractions
  • Inversion = business logic owns the interface, implementation adapts to it
  • DIP ≠ DI ≠ IoC: DIP — principle, DI — mechanism of passing, IoC — framework controls flow
  • Constructor Injection — gold standard: fail-fast, final fields, JIT optimizations
  • Circular Dependencies — common bug with DIP; solution: third coordinator or @Lazy
  • GraalVM AOT compilation resolves dependencies at build time, removing reflection overhead

Common follow-up questions:

  • DIP vs DI — what’s the difference? — DIP — architectural principle (WHO to depend on), DI — technical technique (how to pass)
  • Why is Constructor Injection better than Field Injection? — Fail-fast, immutability (final), safe publication, easy mocking
  • What to do with Circular Dependency? — Refactoring: introduce coordinator, use events, @Lazy as workaround
  • When is DIP NOT needed? — Simple utilities, stable dependencies (String), prototypes

Red flags (DO NOT say):

  • “DIP means creating an interface for every class” (Abstractions Overkill, YAGNI violation)
  • “Spring automatically solves all DIP problems” (Spring is a tool, design is up to the developer)
  • “Field Injection via @Autowired is fine” (hidden dependencies, no safe publication)

Related topics:

  • [[16. How Dependency Inversion principle is related to Dependency Injection]]
  • [[15. How SOLID helps in code testing]]
  • [[7. What is Interface Segregation principle]]
  • [[9. Why do we need SOLID principles at all]]