Dependency Inversion Principle (DIP)
Depend on abstractions, not on concretions — and own the abstractions from the high-level policy.
Why It Matters
- Testability — Swap
MySqlOrderRepositorywithInMemoryOrderRepositoryin 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
class OrderService {
private final MySqlOrderRepository repo =
new MySqlOrderRepository(); // concrete — hard to test, hard to swap
} 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 { ... } // testsDIP 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)
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);
}
}
}The Right Way
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
- 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.
- More interfaces and wiring — Spring / Guice help but add learning curve.
- Wrong abstractions leak — if
OrderRepositorymirrors SQL too closely, you only moved the problem.
Direct dependency vs inverted dependency
| Aspect | App → Concrete | App → Interface ← Concrete |
|---|---|---|
| Compile-time coupling | To MySQL driver / SQL | To your OrderRepository only |
| Unit test | Embedded DB or heavy mocks | In-memory fake |
| Swap datastore | Edit OrderService | New adapter class only |
| Who owns the type? | Infrastructure owns API | Domain owns port |
Common Confusions Cleared Up
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.
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
@Autowiredsupport DIP?
Quick Cheat Sheet
new ConcreteDependency() inside domain logic.