How do the Stable Abstractions Principle (SAP) and the Stable Dependencies Principle (SDP) work together, and how does that combination express the Dependency Inversion Principle at the component level?
answer
- SDP: depend toward stability, I decreases along arrows
- SAP: stable ⇒ abstract
- SDP + SAP = DIP at component scale
- Ports in the stable core, adapters as plugins
- Source dependency opposite the runtime call flow
basics
~20 sSDP says depend in the direction of stability — point dependencies at things that are hard to change. SAP says those stable things should be abstract. Put together: dependencies flow toward abstractions, which is exactly what the Dependency Inversion Principle asks for, applied to whole components.
solid answer
~50 s**SDP** (Stable Dependencies Principle): "depend in the direction of stability" — a component should only depend on components at least as stable as itself, i.e. instability I should decrease as you follow dependency arrows. It prevents a volatile module from becoming a load-bearing dependency for something even harder to change. **SAP** (Stable Abstractions Principle): "a component should be as abstract as it is stable." SDP alone would let you funnel all dependencies into a rigid concrete core — the Zone of Pain. SAP closes that hole by requiring the stable end to consist of interfaces, so it can absorb change by extension rather than modification. Composed: **dependencies point toward stability, and stability implies abstraction, therefore dependencies point toward abstractions** — the component-scale statement of DIP ("depend on abstractions, not concretions"). Architecturally this yields plugin/hexagonal/clean layouts: policy in stable abstract components, detail in unstable concrete plugins that depend inward, wired at runtime.
code
text · 10 linesorders-api (stable, abstract) A=0.80 I=0.00 <-- ports: OrderRepository, PaymentGateway
^ ^
| | source-code dependencies point INWARD (SDP + SAP)
orders-postgres payments-stripe A=0.00 I=1.00 <-- adapters: concrete, volatile
^ ^
| |
app-main (most unstable, most concrete: names every concretion and wires them)
# runtime call flow goes the OTHER way: orders-api's use case calls into the adapter
# through the port -> that inversion is DIP applied to whole deployable components.go deeper
State both principles in one sentence each and give the conclusion: dependencies end up pointing at abstractions, like DIP but for whole modules.
Explain why each principle alone is insufficient (Zone of Pain vs Zone of Uselessness) and give the api/impl component split as the mechanism.
Map it onto hexagonal/clean architecture: ports in the stable core, adapters as plugins, source dependency opposite to runtime call flow, cycles as SDP-breakers, structural enforcement over metrics.
Discuss where inversion is not worth its indirection, how runtime coupling (shared DB, message schemas) evades the whole analysis, versioning and release cadence of the api component, and how team ownership boundaries should align with the stable/unstable split.
### The three principles involved **SDP — Stable Dependencies Principle.** *Depend in the direction of stability.* Using instability `I = Ce / (Ca + Ce)` (Ce = outgoing couplings, Ca = incoming couplings; I = 0 is maximally stable, I = 1 maximally unstable), SDP says: for every dependency edge `X → Y`, `I(X) ≥ I(Y)`. You may depend on something harder to change than you; you must not depend on something *easier* to change than you. Violating it means a volatile component sits underneath a rigid one — its churn now forces rebuild, retest, and redeploy of everything above. **SAP — Stable Abstractions Principle.** *A component should be as abstract as it is stable.* Using `A = Na / Nc`, abstractness should rise as I falls. **DIP — Dependency Inversion Principle** (one of the SOLID class-level principles). *Depend on abstractions, not on concretions; high-level policy must not depend on low-level detail — both should depend on abstractions.* ### Why SDP alone is insufficient SDP is purely about **direction**. Follow it faithfully and every arrow converges on your most stable components — but SDP never says what those components should *contain*. If they contain concrete, volatile implementation, you have built the perfect trap: a component that everything depends on, that cannot be extended without editing, and that must be edited on every business change. That is the **Zone of Pain** (`A ≈ 0, I ≈ 0`), and SDP is fully satisfied inside it. ### Why SAP alone is insufficient SAP is purely about **content**. Make components abstract in proportion to their stability, but let dependency arrows point at volatile modules and you still get cascading rebuilds and rigid coupling — plus abstract components nobody depends on (`A ≈ 1, I ≈ 1`, the **Zone of Uselessness**). ### The composition ``` SDP: dependencies point toward stability SAP: stability implies abstraction ---------------------------------------- ⇒ dependencies point toward abstractions == DIP, at component granularity ``` This is why Martin presents SDP and SAP as a pair: together they are DIP scaled from classes to deployment units. DIP tells a class to hold an interface reference rather than a concrete one; SDP+SAP tell an entire jar/module to depend on a contracts component rather than an implementation component. ### What it looks like architecturally This composition *is* the plugin architecture that hexagonal (ports & adapters), onion, and Clean Architecture all describe: - **`domain` / `application` (stable, abstract)** — entities, use cases, and **ports**: `PaymentGateway`, `OrderRepository`, `Clock`. Many dependents, few or no outgoing dependencies, high A. `I ≈ 0, A ≈ 0.7–1`. - **`infrastructure` adapters (unstable, concrete)** — `StripePaymentGateway`, `PostgresOrderRepository`, framework glue. Nothing depends on them; they depend inward. `I ≈ 1, A ≈ 0`. - **Composition root / `main`** — the most unstable, most concrete component of all; it knows every implementation and wires them into the abstractions at startup. Correctly at the extreme end of the Main Sequence. The key trick: the source-code dependency of `infrastructure → domain` points *opposite* to the runtime call flow (`domain` code calls into `infrastructure` at runtime, through the port). That inversion is what buys the architecture its independence from frameworks and databases. ### Practical mechanics - **Split api from impl.** `orders-api` (interfaces + value types, stable, abstract) and `orders-postgres` (implementations, unstable, concrete). Consumers depend on the api component only. - **Own the interface on the consumer side.** If a high-level module needs persistence, the *port* belongs to the high-level component, not the database component — otherwise the arrow points the wrong way and SDP is violated. - **Break cycles.** A dependency cycle makes SDP unsatisfiable (I can't decrease all the way around a loop). Break it by extracting a shared abstraction component or applying the Acyclic Dependencies Principle's dependency-inversion escape hatch. - **Enforce structurally.** Metrics can be gamed; direction rules cannot. Use ArchUnit, Spring Modulith verification, dependency-cruiser, ESLint import boundaries, or Java module declarations to encode the allowed arrows. ### Nuances and honest caveats - Not every dependency needs inverting. Inverting toward stable, non-volatile third-party libraries (a math library, a JSON codec) usually adds indirection for no benefit — invert against **volatile** or **substitutable** detail. - Runtime coupling escapes the analysis entirely: two components with no import edge can still be tightly coupled through a shared database, a message schema, or an HTTP contract. - The composition-root component intentionally violates "depend on abstractions" — someone has to name the concretions. That is by design, and it should be the single most unstable component in the system.
- If SDP is satisfied everywhere but SAP is ignored, what specifically goes wrong?You get a correctly oriented dependency graph converging on a rigid concrete core — the Zone of Pain. Every business change edits the component everything depends on, forcing system-wide recompile, retest, and redeploy. The direction is right, the content is wrong.
- How do you satisfy SDP when a dependency legitimately has to run in the 'wrong' direction — say, a stable domain module needs to send an email?Invert it. The domain declares the port (an `EmailSender` interface) inside its own stable, abstract component; the concrete SMTP adapter lives in an unstable component that depends on the domain and implements the port. The source dependency now points inward toward stability even though the runtime call goes outward. Alternatively, publish a domain event and let an unstable subscriber react.
- What breaks when two components form a dependency cycle?SDP becomes unsatisfiable — instability cannot strictly decrease all the way around a loop — and neither can be built, tested, or released independently. This is the Acyclic Dependencies Principle. Fix by extracting a shared abstraction component that both depend on, or by inverting one edge with an interface.
saying these in an interview costs you the question
- Stating SDP as 'depend on things that change often' — it's the reverse: depend toward stability
- Assuming SDP alone prevents rigidity; without SAP it produces a perfectly directed Zone of Pain
- Putting the port interface in the low-level component, which points the dependency arrow the wrong way
- Claiming DIP only applies to classes and has no component-level analogue
- Treating the composition root's dependence on concretions as a violation rather than its intended role
- Inverting every dependency, including non-volatile third-party libraries, purely for principle