Skip to main content

Boundaries & Dependency Inversion

beginner12 min readLesson 199 of 204

High-level policy depends on interfaces; details plug in from below. The seam is what you can test, swap, and version.

The dependency rule

Business logic must not include storage, transport, or vendor headers. It defines the interface it needs; an adapter below implements it:

struct Clock {                      // the seam โ€” owned by the logic layer
    virtual ~Clock() = default;
    virtual std::int64_t nowMs() const = 0;
};

struct RateLimiter {                // high-level policy, testable
    explicit RateLimiter(const Clock* clock) : clock_(clock) {}
    bool allow(std::int64_t nowMs); // deterministic under a fake clock
    const Clock* clock_;
};

Production binds a real clock at composition time; tests bind a fake. The seam is why the logic is deterministic, and determinism is why it is testable โ€” flaky time-based tests are a missing seam, not bad luck.

Interfaces at boundaries

An abstract interface is also an ABI boundary (module 16): the vtable layout is the contract. Keep them small, stable, and versioned โ€” a wide interface that grows entry-by-entry is how vtables end up "frozen forever".

What belongs where

  • Domain: types + rules, no I/O includes.
  • Adapters: DB/queue/fs implementations of domain interfaces.
  • Composition root: the one place that wires concrete types.

When a new requirement touches three layers, ask which dependency pointed the wrong way.

Now practice

Practice: Architecture MechanicsA token bucket made deterministic by a clock seam, a bus whose dispatch order is a guarantee, and a config ladder with provenance.2 challenges ยท ยท ~20 min