Design Patterns Tutorial 0/70 lessons ~6 min read Lesson 14

    Dependency Inversion

    Dependency Inversion makes testing and infrastructure swaps possible.

    Course progress0%
    Focus
    21 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Dependency Inversion makes testing and infrastructure swaps possible. Without DIP, domain code calls Postgres, Stripe, and Slack directly. With DIP, domain depends on OrderRepository, PaymentGateway, and Notifier — infrastructure implements those abstractions.

    The story

    Every service used new StripeClient() inline. Tests needed Stripe sandbox, ran four minutes, flaked often. One sprint of interfaces + DI made tests offline — twelve seconds, zero flakiness.

    The business problem

    Concrete dependencies in domain code cause:

    • Slow flaky tests tied to external systems.
    • Vendor lock-in — Stripe types leak into domain.
    • Impossible local dev without full infra stack.
    • Big-bang migrations when swapping database or provider.

    The problem teams faced

    Pre-DIP architecture:

    • Domain imports SDK headers/types.
    • Services construct dependencies with `new`.
    • Tests spin real DB/API or skip coverage.

    Understanding the topic

    DIP rule: high-level modules define abstractions; low-level modules implement them. The dependency arrow inverts.

    • Domain — owns interface (port).
    • Infrastructure — implements interface (adapter).
    • Composition root — only place knowing both sides.

    Internal architecture

    Layer dependencies (Clean Architecture):

    text
    HTTP Controller → Use Case → Domain Entity
    ↓ depends on
    Repository interface (domain layer)
    ↑ implemented by
    PostgresRepository (infra layer)

    Visual explanation

    DIP in three diagrams:

    Dependency inversion
    Domain
    High-level
    Abstraction
    Domain-owned
    Infrastructure
    Implements
    Tests use fakes
    Same interface
    Arrow points inward — infrastructure depends on domain, not reverse.
    Hexagonal view
    Domain core
    Business rules
    Ports
    DIP interfaces
    Adapters
    Postgres · Stripe
    Driving adapters
    HTTP · CLI
    Ports = DIP; adapters = infrastructure implementations.
    DIP vs DI
    DIP
    Principle
    Constructor inject
    Mechanism
    DI container
    Scale tool
    Composition root
    Wiring point
    DIP is what; DI is how; container optional at small scale.

    Informative example

    Before / after DIP:

    ts
    // ❌ Domain depends on concretion
    class PlaceOrder {
    async run(cart: Cart) {
    const stripe = new Stripe(process.env.STRIPE_KEY);
    await db.query("INSERT ...");
    await stripe.charges.create({ amount: cart.total() });
    }
    }
    // ✅ Domain depends on abstraction
    interface PaymentGateway { charge(amount: Money): Promise<Receipt>; }
    interface OrderRepository { save(order: Order): Promise<void>; }
    class PlaceOrder {
    constructor(
    private payments: PaymentGateway,
    private orders: OrderRepository,
    ) {}
    async run(cart: Cart) {
    const receipt = await this.payments.charge(cart.total());
    await this.orders.save({ cart, receipt });
    }
    }

    Execution workflow

    1Introduce DIP incrementally
    1 / 5

    List external needs

    DB, payment, email, clock, random.

    Every `new` in domain is candidate.

    Real-world use

    Clean Architecture, Hexagonal, and Onion are DIP at scale. NestJS modules, Spring Boot, ASP.NET Core enforce DIP by convention. Repository and Gateway patterns are named DIP applications.

    Production case study

    Stripe-coupled codebase rescue (above): tests 4 min → 12 sec; flakiness eliminated; Postgres swapped to in-memory in CI.

    Trade-offs

    • Fast reliable tests; swappable infra.
    • More interfaces and wiring ceremony.
    • Composition root complexity at scale — DI container helps.

    Decision framework

    • DIP whenever domain touches DB, HTTP, queues, time, randomness.
    • Skip on pure functions and scripts with no tests.

    Best practices

    • Domain folder must not import `stripe`, `pg`, `@aws-sdk`.
    • Factory functions work for DIP in functional style: `(deps) => (input) => result`.
    • Contract tests on adapters, unit tests on domain with fakes.

    Anti-patterns to avoid

    • Interface in infra layer that domain imports — inversion backwards.
    • Service locator hidden inside domain classes.

    Common mistakes

    • DIP everywhere on day one greenfield — inject real deps first, extract when test pain hits.

    Debugging tips

    • Production vs test behavior? Diff composition root wiring.

    Optimization strategies

    • Manual constructor injection until ~10 wired services — then consider container.

    Common misconceptions

    • DIP ≠ DI framework required. Constructor params suffice.
    • Domain owns the port — not the database team.

    Advanced interview questions

    Interview Prep

    Practice concise answers, then expand each card for the explanation.

    2 questions
    1AdvancedQuestionWho owns the interface in DIP?+

    Answer

    High-level/domain — interface expresses what domain needs; infra adapts.

    Follow-up

    Show Clean Architecture folders.
    2AdvancedQuestionDIP enable Hexagonal?+

    Answer

    Ports = domain interfaces; adapters = infra. Hexagon shows infra surrounding domain.

    Follow-up

    Driving vs driven adapter?

    Summary

    You can invert dependencies, wire at composition root, and test with fakes. Tell the four-minute-to-twelve-second test story.

    Ready to mark this lesson complete?Track your journey across the entire course.