THIS EXPLANATION
THE ROOM
CDA·72 Computing, Data & AI 6 MIN · 8 STATIONS

Dependency direction

A Socratic walk-through of dependency direction — reasoned out one step at a time, not lectured.

abcdefgh
a

The question we started with

THE QUESTION #

Why should the part of a program that stores data depend on the part that makes decisions, and not the reverse?

Draw the layers of an ordinary application the way most people first draw them: user interface on top, business rules beneath, database at the bottom. Arrows point downward. The rules call the database, because the rules need the data. That is not a naive picture — it is how the code reads, line by line, when you follow a request through it.

Then someone tells you the arrow between the bottom two should point up: the storage code should depend on the rules, not the other way round. But the rules still need the data. How can a thing use another thing without depending on it? The puzzle only dissolves once we admit that "depends on" and "calls" are not the same relation, and the whole argument lives in that gap.

b

Reasoning it through

REASONING #

Start with what a dependency costs. If module A names a type defined in module B, then A cannot compile without B, cannot be tested without some version of B, and must change whenever B's shape changes. The arrow is a statement about coupling in the source, not about who invokes whom at runtime.

Now ask a question of comparative rates. How often do your business rules change? A competitor moves, a regulation shifts, someone finds a better way to price a thing. And how often does your storage technology change? Rarely — but when it does, you would very much like the rules not to notice. Notice the asymmetry: you want the thing that changes for external, technical reasons to be the thing that absorbs change, and the thing encoding what your organisation is actually for to be the thing left alone. A dependency arrow propagates change against its own direction: if storage depends on rules, a storage swap cannot reach the rules; if rules depend on storage, it can.

So the arrow should point from the volatile-for-technical-reasons part toward the stable-for-business-reasons part. That is a design goal. The mechanism that achieves it is the interesting bit.

The move is to define, inside the rules, an interface describing what the rules need — save this order, find the customer with this identifier — stated purely in the rules' own vocabulary, with no mention of tables, connections, or rows. The rules call that interface. The storage module then implements it. Now trace the two relations separately. At runtime, control flows from rules into storage: the rules do call the database, exactly as before. In the source, storage names an interface owned by the rules, and the rules name nothing owned by storage. The arrow has flipped while the call direction has not moved an inch. That is the whole trick, and it is why "dependency inversion" sounds paradoxical until you separate the two relations.

Ask what has to be true for this to work. The interface must be expressed in the language of the domain, not the language of the database. If the rules define an interface with executeQuery and resultSet on it, nothing has been inverted — the dependency is still on a relational database, merely spelled differently, and you will discover this the day someone tries to implement it over a message queue or a third-party API. This failure is common enough to have a name: a leaky abstraction that has moved the coupling rather than removed it.

What does this buy that is concrete rather than aesthetic? Testing changes character: the rules can be exercised against an in-memory implementation of their own interface, with no database, no fixtures, and no I/O, and the speed is structural rather than a matter of discipline. Substitution becomes local: swapping Postgres for DynamoDB touches one implementation — a claim often oversold, since if your rules assumed transactional consistency and the new store does not offer it no interface will save you, but the code boundary is real even when the semantic boundary is not. And the domain becomes readable, because opening the rules module shows the vocabulary of the business uninterrupted by framework annotations and connection handling.

Generalise the pattern and you get hexagonal architecture — Alistair Cockburn's ports-and-adapters, named in 2005. The domain sits in the middle and defines ports, which are just these interfaces. Everything technical — the web layer, the database, the message broker, the clock — is an adapter implementing or driving a port. Every arrow in the source points inward. The much-drawn concentric-circles version of the same idea, popularised as clean architecture, states it as one rule: source dependencies point only toward higher-level policy.

c

The analogy

THE ANALOGY #
THE FIGURE

Think of a company that hires a supplier. The company writes the specification — these dimensions, this tolerance, delivered on these dates — and the supplier builds to it. The specification is the company's document, in the company's terms, and the supplier is the party who has to read it and conform. The company still depends on getting parts, and the parts still flow from supplier to company; but nobody would say the specification was written by the supplier. Change suppliers and the specification is untouched.

WHERE IT BREAKS DOWN

a supplier can be swapped only because the specification captured everything that mattered, and in software the properties hardest to specify — transaction semantics, latency, failure modes — are exactly the ones an interface tends to leave unsaid, so the swap that looks free on paper often is not.

d

Clarifying the model

THE MODEL #

The misconception worth correcting head-on is that this is about adding an interface for every class. It is not. An interface between two things that always change together buys nothing and costs a layer of indirection; the technique earns its keep precisely at boundaries where the two sides have different reasons to change. Applied uniformly, it produces the codebase everyone complains about, where each concrete class has a single implementing interface and no question is answerable without three jumps.

A second refinement: the direction is chosen from stability, not from importance or from position in a diagram. "High-level" in this literature means "further from the machine and closer to the policy the organisation cares about" — so the rule is really that source dependencies should run from the more volatile toward the more stable. In a library whose whole purpose is a storage engine, the storage code is the stable policy, and inverting it would be nonsense.

One honest caveat. There is no experimental evidence that inverted dependencies produce fewer defects; what exists is practitioner consensus and a mechanical argument about which changes propagate where. Treat it as a heuristic with a known cost — indirection — not a proven law.

e

A picture of it

THE PICTURE #
Dependency direction
Dependency direction Follow the arrowheads, because every one of them is a source-code dependency. PlaceOrder points only at the interface beside it; the Postgres class points upward at that same interface, which is the inversion -- the concrete storage names the domain's contract and the domain names nothing concrete. At runtime the calls run the other way, from PlaceOrder down into the Postgres class, and nothing in this picture shows that, which is exactly the distinction the diagram is here to make. {"generator":"[email protected]","source":"../Socrates/.diagram-cache/_src/dependency-direction.md","sourceIndex":1,"sourceLine":4,"sourceHash":"46268b031733b8158467cac10d84abf1d682981c53b8edbe4cbd13303ef7d9ce","diagramType":"class","layoutVariant":"source","repairedDuplicateIds":[],"motion":"entrance-with-reduced-motion-fallback","presentation":"editorial","attempt":1,"viewBox":{"x":0,"y":0,"width":1083,"height":757},"qa":{"passed":true,"findings":[]}} calls implements drives PlaceOrder +execute(basket) : Confirmation «interface» OrderRepository +save(order) +findByCustomer(id) : Order PostgresOrderRepository +save(order) +findByCustomer(id) : Order HttpOrderController +post(request) Declared inside the domain, inthe domain's words

How to readFollow the arrowheads, because every one of them is a source-code dependency. PlaceOrder points only at the interface beside it; the Postgres class points upward at that same interface, which is the inversion — the concrete storage names the domain's contract and the domain names nothing concrete. At runtime the calls run the other way, from PlaceOrder down into the Postgres class, and nothing in this picture shows that, which is exactly the distinction the diagram is here to make.

f

What became clearer

WHAT CLEARED #
WHAT CLEARED

"Depends on" is a compile-time relation and "calls" is a runtime one, and once you stop conflating them the paradox disappears: a module can invoke another while depending only on an interface it owns. Pointing dependencies at the parts with the fewest reasons to change means change stops at the boundary instead of travelling inward.

g

Where to go next

ONWARD #
  • How a repository interface interacts with transactions that must span several aggregates, which is where the pattern is most often abandoned.
h

Key terms

TERMS #
TermWhat it means
Dependency inversionarranging source-code dependencies to point from concrete, volatile modules toward abstractions owned by higher-level policy.
Portan interface declared by the domain describing what it needs from the outside world, in the domain's own vocabulary.
Adaptera concrete implementation of a port, holding all the technology-specific detail on the outside of the boundary.
Hexagonal architectureCockburn's ports-and-adapters arrangement (2005) in which every source dependency points inward toward the domain.

Every term the collection defines is gathered in the glossary.

Nearby on the shelf

4