Case file A11

Dependency Inversion Principle

Depend on abstractions.

High-level policy doesn't depend on volatile detail. Both hinge on a stable abstraction. The concrete adapters sit at the edges. The inside of the system is built from interfaces.

ByAdam LewisPublished3 May 2026Revised13 Oct 2026Reading12 minVersionv1.2ConfidenceHigh
§0b

Opinion

I have lost track of the times I have refactored a service that “just” called the database directly into one that depends on a repository interface. The line-of-code count barely changes. The testability changes by an order of magnitude. DIP is not a bureaucratic detour: the rule allows a real database to be swapped for an in-memory fake during a test, a third-party email service for a no-op during a CI run, an HTTP client for a recorded fixture during local development. Every team that releases software has rediscovered this rule the hard way. The only argument left is whether to teach it once or pay for it forever.

The acronym confusion is the load-bearing one. SOLID's D is class-level: depend on an abstract type rather than a concrete one. The Stable Dependencies Principle is package-level: depend in the direction of stability.6 Martin himself spells out the link: SDP plus the Stable Abstractions Principle (SAP) is “DIP for components”. That is an analogy rather than an identity. SDP asks which folder may import which folder; DIP asks what shape that import should be. Two graphs, one engineer who writes both.

Pair DIP with A9 Liskov Substitution: the abstraction depends on the concrete adapter honouring its contract. Pair it with A10 Interface Segregation: the abstraction must be narrow enough to satisfy without leaking detail. Pair it with A5 Thin Handlers: the handler is a transport adapter and runs at the edge. Pair it with A2 Three-Tier Hoisting: the inward-pointing import direction the tier system codifies.

Copy a note and link

Grab this short comment and drop it into a PR comment or an LLM chat to prompt the right change.

High-level policy doesn't depend on volatile detail. Both hinge on a stable abstraction. Domain at the centre talks only to interfaces. Concrete adapters sit at the edges. Apply where volatility lives (database, email, third-party API) and leave alone where it doesn't.

/tenet/dependency-inversion-principle/A11
Copy for paste
§0c

AI eyes only

Rule: depend on abstractions, not concretions. Volatile details are injected, never instantiated.

Reject: new ConcreteService() inside a constructor or function body. Reject: hard-coded URLs, file paths or SDK clients wired in directly. Reject: importing a database driver from a domain function.

Generate: constructor or function parameters typed as interfaces. The composition root wires the concrete implementation. Test code injects fakes against the same interface.

Diagnostic: if the unit cannot be tested without the database itself, network, or filesystem, the dependency is concrete and must be inverted.

§0d

Why?

  • Testability follows for free. The high-level code accepts its dependencies. Tests pass fakes. A unit test runs in milliseconds because the expensive boundary is mocked at the abstraction.
  • Adapters are replaceable when the vendor changes or the third-party service breaks. Switch from PostgreSQL to MySQL, from SendGrid to Resend, from REST to GraphQL: the domain layer doesn't notice.
  • The domain layer reads as the business problem, not as the integration shim. The names in the inner code are the names the domain expert uses. The integration details live at the edges where they belong.
  • Pairs with A2 Three-Tier Hoisting. Inward-pointing imports follow from inward-pointing dependencies. The tier system is DIP applied to the file tree.
  • Faults are isolated. A vendor outage takes down one adapter. The domain layer runs against a degraded-mode fake or a circuit breaker. The blast radius is the boundary, not the application.
  • CI cost drops. Tests don't need a real database, real email service or real third-party API to run. The slow tests stay at the integration layer. The fast tests cover the domain comprehensively.
  • Plays well with Hexagonal, Onion and Clean Architecture. The architectural shape is the rule's consequence rather than its cause. The rule is the dependency direction at the seams.
  • Agents reach for the concrete by default. With DIP loaded, every constructor that takes a concrete service is flagged. The model writes the abstraction, the wiring and the test double in one pass instead of three reviews.
The receipts
Origins, quoted passages, evidence, the strongest counter-argument and the reply.
§1

Origins

Robert C. Martin's “The Dependency Inversion Principle,” C++ Report May/June 1996, gave the rule the name and the canonical statement. The two clauses appear in paragraph one of the article: “A. High-level modules should not depend upon low-level modules. Both should depend upon abstractions. B. Abstractions should not depend upon details. Details should depend upon abstractions.”1 Martin justifies the word “inversion” on the same page: traditional structured-design methods produce systems where high-level modules depend on low-level modules. Well-designed object-oriented systems invert that dependency. The rule landed in book form in Agile Software Development (2002) as the “D” of SOLID.

The architectural shape that follows from DIP appeared in three near-simultaneous formulations. Alistair Cockburn's Hexagonal Architecture (2005) names the ports-and-adapters pattern: the application's domain hexagon depends on no I/O and the adapters at the edges depend on the domain's ports.2 Jeffrey Palermo's Onion Architecture (2008) presents the same arrangement as concentric rings, with the core depending only on what is inside it.3 Robert C. Martin's Clean Architecture (2017) restates both as the “Dependency Rule”: source code dependencies must point inward, toward the stable, abstract centre.4 Three names, one architecture; DIP is the rule that makes the arrows point the right way.

The European tradition reached the same place from contracts. Bertrand Meyer's Object-Oriented Software Construction (1988, 2nd ed. 1997) treats the supplier as providing a service to the client. The client depends on the contract rather than the implementation. DIP is the dependency-graph expression of the same client-supplier thinking; the European pre-condition / post-condition tradition and the American interface / implementation tradition were always describing the same shape from two angles.

The practical follow-on is dependency injection. Martin Fowler's 2004 article Inversion of Control Containers and the Dependency Injection Pattern separated the principle from the technique: DIP is the design rule, DI is the wiring technique that satisfies it.7 Spring, Guice, Dagger, NestJS, FastAPI, ASP.NET Core all provide DI as a first-class container concept. The rule that motivated them is the rule the SOLID literature named two decades earlier.

§2

Quotes

High-level modules should not depend upon low-level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.

Robert C. Martin · The Dependency Inversion Principle (1996)

Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases.

Alistair Cockburn · Hexagonal Architecture (2005)

DI is about wiring, IoC is about direction, and DIP is about shape.

Brett Schuchert / Martin Fowler · DIP in the Wild (2013)

Static utility classes and singletons are inappropriate for classes whose behavior is parameterized by an underlying resource.

Joshua Bloch · Effective Java 3e (2018), Item 5
§3

Evidence

16 external sources, ranked by author authority. The first five are the canon; expand to see the rest, including the qualifiers and the named opposers. Each links out to its primary source.

  1. 01
    Robert C. Martin · 1996
    The original. Two clauses. The canonical published statement of the rule.
  2. 02
    Robert C. Martin · 2017
    The Dependency Rule. Source code dependencies must point inward. The abstract centre is stable, the concrete edges are volatile.
  3. 03
    Alistair Cockburn · 2005
    Ports and adapters. The application's domain hexagon depends on no I/O. Adapters at the edges depend on the domain's ports.
  4. 04
    Jeffrey Palermo · 2008
    Same arrangement as concentric rings. The core depends only on what is inside it.
  5. 05
    Bertrand Meyer · 1997
    Client-supplier contracts. The client depends on the contract rather than the implementation. The European route to the same shape.
  1. 06
    Martin Fowler · 2004
    Separates the principle (DIP) from the technique (DI). The pragmatic essay that gave the working-developer reading of the SOLID rule.
  2. 07
    Harry Percival & Bob Gregory · 2020
    Worked-example DIP / DI / Hexagonal in Python. The pragmatic counter to the perception that the rule is Java-shaped.
  3. 08
    Joshua Bloch · 2017
    Item 64 “Refer to objects by their interfaces”; Item 5 “Prefer dependency injection to hardwiring resources.” Java-shaped operationalisation of the rule.
  4. 09
    John Ousterhout · 2018
    The over-engineering critique. Pass-through methods and shallow abstractions are debt. The rule is right when applied to volatile detail and wrong when applied to stable detail.
  5. 10
    Brett Schuchert / Martin Fowler · 2013
    The canonical disambiguation: DI is about wiring, IoC is about direction, DIP is about shape. Most arguments about DIP are arguments about one of the others.
  6. 11
    Rich Hickey · 2011
    Simplicity-first reading. Hickey treats the SOLID principles as additional complexity; DIP justifies itself on volatile boundaries and not elsewhere.
  7. 12
    Vaughn Vernon · 2013
    Repository pattern as DIP applied to persistence. The domain depends on the repository interface; the infrastructure layer implements it.
  8. 13
    Sam Newman · 2021
    Inter-service contracts as architectural-scale DIP. The contract is the abstraction. Service implementations are the concrete adapters.
  9. 14
    Wikipedia · 2024
    Cleanest single-page reference for the technique side of the rule. Constructor / setter / interface injection patterns with worked examples.
  10. 15
    Wikipedia · 2024
    The principle side of the rule. Cross-references to SDP, SAP, Hexagonal and Onion. The cleanest single-page disambiguation in print.
  11. 16
    Jim Blandy & Jason Orendorff · 2021
    Rust's trait-bound generics make DIP a type-system property: `fn store<R: UserRepo>(repo: R)` inverts the dependency at the language level.

Sixteen sources spanning Martin's 1996 paper and the SOLID canon, plus the Hexagonal/Onion/Clean architecture lineage (Cockburn, Palermo) and Meyer's contract tradition. Practical examples from the Java/Python ecosystems sit further down. The qualifiers hold the over-engineering critique. The opposers hold the “DI is just wiring; DIP is overhead” reading from working teams who have shipped without it.

§4

Examples

Viewing: TypeScript.
Avoid

Before HedgehogService news up Postgres. Untestable.

Filehedgehog-service.ts
import { PostgresClient } from "pg-driver";

export class HedgehogService {
  private readonly db: PostgresClient;
  constructor() {
    this.db = new PostgresClient({ url: process.env.DB_URL });
  }
  rescue(h: Hedgehog): Promise<void> {
    return this.db.query("INSERT INTO hedgehogs VALUES ($1, $2)", [h.id, h.weight]);
  }
}
Prefer

After HedgehogService depends on a port. Postgres adapter sits at the edge.

Filehedgehog-service.ts
interface HedgehogStore {
  save(h: Hedgehog): Promise<void>;
}

export class HedgehogService {
  constructor(private readonly store: HedgehogStore) {}
  rescue(h: Hedgehog): Promise<void> {
    return this.store.save(h);
  }
}

export class PostgresHedgehogStore implements HedgehogStore {
  constructor(private readonly db: PostgresClient) {}
  save(h: Hedgehog): Promise<void> {
    return this.db.query("INSERT INTO hedgehogs VALUES ($1, $2)", [h.id, h.weight]);
  }
}
§4b

Enforcement

Viewing: TypeScript.

Apply these rules in eslint.config.mjs. The full enforcement across every tenet lives on the implementation page.

RuleToolCatches
import/no-restricted-pathseslint-plugin-importdomain code importing from infrastructure or adapters. The DIP violation at the file-tree level: the import direction is wrong.
no-restricted-syntax (new SmtpClient)ESLint coredomain code instantiating concrete clients. The high-level module is depending on a low-level detail directly.
@typescript-eslint/no-explicit-anytypescript-eslintany in interface declarations. Often the smell of a too-wide abstraction that does not actually invert anything.
@typescript-eslint/explicit-module-boundary-typestypescript-eslintmissing types on module-boundary functions. The boundary between layers must be explicit in the type system; implicit types are an unstated abstraction.
import/no-default-export (in adapters)eslint-plugin-importdefault exports in adapter files. Default exports hide which contract the adapter is implementing; named exports keep the abstraction explicit.
eslint.config.mjsconfiguration snippet
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
import importPlugin from 'eslint-plugin-import';

export default defineConfig({
  files: ['**/*.{ts,tsx}'],
  extends: [tseslint.configs.base],
  plugins: { import: importPlugin },
  rules: {
    'import/no-restricted-paths': ['error', {
      zones: [
        {
          target: './src/domain',
          from: ['./src/infrastructure', './src/adapters'],
          message: 'Domain must not import from infrastructure or adapters, DIP violation.',
        },
      ],
    }],
    '@typescript-eslint/no-explicit-any': 'error',
    '@typescript-eslint/explicit-module-boundary-types': 'error',
    'no-restricted-syntax': ['error', {
      selector: 'NewExpression[callee.name=/^(SmtpClient|HttpClient|MysqlClient|RedisClient)$/]',
      message: 'Inject the abstraction; do not instantiate concrete clients in domain code.',
    }],
  }
});
§4c

AI rules

File.cursor/rules/a11-dependency-inversion.mdc
---
alwaysApply: true
---

## Prickles A11: Dependency Inversion Principle

Depend on abstractions. High-level policy doesn't depend on volatile detail. Both hinge on a stable abstraction.

Concrete adapters sit at the edges. The domain at the centre talks only to interfaces. The integrations live where they belong: at the boundary.

Apply the rule where volatility lives. Invert the database, the email provider and the third-party API client. Leave stable utilities, time and console output alone unless tests need a fake.

Refuse a constructor that takes a concrete service when a contract would do. Refuse a `new SmtpClient()` in domain code. Refuse an import that points inward.

Repo layout, CI, and ESLint wiring for these paths live on /implementation, not repeated on every tenet.

§5

Counter-argument

Counter

The strongest steelman is the over-engineering counter. John Ousterhout's A Philosophy of Software Design argues that excess interfaces are a cost: each abstraction is a layer of indirection a reader has to navigate, a name to remember, an extra file to maintain.5 “Pass-through” methods that just delegate to the next layer are the canonical anti-shape. An abstraction that exists only because “the SOLID book said so” is debt the team will pay for in onboarding time. The implication for DIP is sharp: the rule is right when the dependency is genuinely volatile, and over-applied when it isn't. Wrapping console.log in a Logger interface that will never be swapped for a different logger is theatre.

§6

Counter-argument retort

Reply

The over-engineering counter is correct on its own terms.5 An abstraction over a stable concrete is theatre. The rule should not be read as “wrap every dependency in an interface”; it should be read as “invert the dependency on volatile detail.” The discipline is naming what is volatile. Database technology is volatile (you might switch from PostgreSQL to MySQL). Email providers are volatile (Mandrill, SendGrid, Resend, depending on the year). Logging targets are volatile only if your operations team is changing observability stacks. Time is rarely volatile but is occasionally faked in tests, so a Clock abstraction justifies itself. Apply DIP where volatility lives. Leave it off where the dependency genuinely isn't going to change.

The “DI is just wiring” objection, that DIP devolves into container configuration and yet another framework dependency, misreads the rule.7 DIP is the design property; DI is one technique that satisfies it.8 Constructor injection without a container is enough.9 Pure factory functions are enough. React's props pattern is dependency injection by another name. The rule is satisfied whenever a high-level module accepts its dependencies through its public surface rather than reaching for them itself. The container is a deployment convenience, not a definition.

The hardest objection is the simplicity-first reading from Rich Hickey, John Ousterhout and the Go community: “most code doesn't need this”. The reply: most code that doesn't need DIP is also code that doesn't have a unit test, doesn't outlive its first vendor change, and doesn't survive a third-party outage. If your codebase genuinely doesn't need testability, replaceability or fault isolation, the rule doesn't apply: you have built a script rather than a system. As soon as the script crosses into either category, the rule starts paying for itself.

The genuine residue is naming and granularity. DIP says depend on an abstraction. It doesn't say which abstraction. Pair it with A10 Interface Segregation: the abstraction must be narrow enough to satisfy without leaking detail. With F2 Intention-Revealing Names: the abstraction's name describes what the consumer needs. And with A2 Three-Tier Hoisting: the inward-pointing import direction the tier system codifies. The four together turn DIP from a slogan into a working practice.

§7

Notes

  1. [1]Robert C. Martin: “The Dependency Inversion Principle,” C++ Report, May/June 1996. The two clauses appear in paragraph one. The canonical published statement of the rule.
  2. [2]Alistair Cockburn: Hexagonal Architecture (2005). Ports-and-adapters: the application's domain hexagon depends on no I/O and the adapters at the edges depend on the domain's ports.
  3. [3]Jeffrey Palermo: “The Onion Architecture: part 1” (2008). The same arrangement as concentric rings, with the core depending only on what is inside it.
Disagree? Found a hole in the argument? Take issue with this tenet →
Last revised: 2026-10-13