DEV Community

Cover image for The $26 Billion Leak: Why Frameworks Are Making Your Codebase Rot (and How Hexagonal Isolation Fixes It)
George Arthur
George Arthur

Posted on

The $26 Billion Leak: Why Frameworks Are Making Your Codebase Rot (and How Hexagonal Isolation Fixes It)

According to a 2024 vFunction survey, over 50% of companies watch more than a quarter of their entire IT budget vanish into technical debt.

This isn't happening because developers are bad at writing code. It's happening because our architectural boundaries are eroding. We're letting business rules bleed into database models, and HTTP controllers bleed into core logic.

The primary culprit? Modern framework marketing and the "tutorial culture" that dictates how we learn to code.

Here's a deep look into why your architecture is bleeding, the engineering principles we're violating, and how Hexagonal Isolation offers a structural cure — with a full working TypeScript codebase, FlowBank, so you can see it applied rather than just described.

1. The Trap: "Time-to-Hello-World" vs. Architectural Debt

Most developers learn to build applications through 15-minute video tutorials or framework quickstarts. To win adoption, frameworks optimize for rapid onboarding — showing you how to build a Todo app in three steps using "magic" shortcuts.

This creates three architectural traps:

  • The Active Record Trap — ORMs couple database schemas directly to business entities. Change your table, and your core business model breaks.
  • The Controller-to-DB Shortcut — Framework boilerplate encourages writing validation, business rules, and SQL directly inside HTTP handlers.
  • The "Magic" Annotation Bleed — Framework-specific decorators injected straight into domain objects make the entire application hostage to that vendor.

Follow these shortcuts long enough and you accumulate Architectural Technical Debt (ATD). Three years later, upgrading your framework or switching database providers means rewriting the foundational business rules of the company.

2. The Solution: What Is Hexagonal Isolation?

Originally coined by Alistair Cockburn as Ports and Adapters, Hexagonal Architecture solves layer bleed by enforcing strict structural isolation.

[External World] ----> ( Port / Interface ) ----> [Core Business Logic]
(HTTP/DB/CLI)          (Strict Abstraction)       (Pure Domain Rules)
Enter fullscreen mode Exit fullscreen mode
  • The Core Domain — Contains nothing but pure business logic. Zero knowledge of the web, databases, file systems, or frameworks.
  • Ports (Abstract Interfaces) — The hexagon's edge is built entirely of abstract interfaces. The core only talks to these.
  • Adapters (Concrete Implementations) — Postgres, REST APIs, GraphQL, S3 — all live outside the hexagon, implementing the ports to feed data in and out.

By isolating the core through abstraction, you get true plug-and-play architecture. Swap MongoDB for PostgreSQL without touching a single line of your core application rules.

A minimal TypeScript example

This is a trimmed-down version of the pattern used throughout FlowBank, a small open-source banking API built specifically to demonstrate this architecture end to end — transfers, deposits, notifications, and payment processing, all with the core fully isolated from Postgres, Stripe, and Express.

// core/ports/user-repository.port.ts
// The PORT — an abstract interface the core depends on, not a concrete DB.
export interface UserRepository {
  findById(id: string): Promise<User | null>;
  save(user: User): Promise<void>;
}

// core/domain/user.ts
// The CORE — pure business logic, no framework or DB imports.
export class User {
  constructor(public readonly id: string, private email: string) {}

  changeEmail(newEmail: string) {
    if (!newEmail.includes("@")) {
      throw new Error("Invalid email");
    }
    this.email = newEmail;
  }
}

// adapters/postgres-user-repository.ts
// The ADAPTER — concrete implementation, swappable without touching the core.
import { UserRepository } from "../core/ports/user-repository.port";
import { User } from "../core/domain/user";
import { db } from "./db-client";

export class PostgresUserRepository implements UserRepository {
  async findById(id: string): Promise<User | null> {
    const row = await db.query("SELECT * FROM users WHERE id = $1", [id]);
    return row ? new User(row.id, row.email) : null;
  }

  async save(user: User): Promise<void> {
    await db.query("UPDATE users SET email = $1 WHERE id = $2", [
      user, // simplified for brevity
      user,
    ]);
  }
}
Enter fullscreen mode Exit fullscreen mode

The User class and its business rules never import db-client, Express, or anything Postgres-specific. Tomorrow, swapping PostgresUserRepository for a MongoUserRepository or an InMemoryUserRepository (for tests) requires zero changes to the core.

3. The Theoretical Pillars: SOLID & DDD

Hexagonal isolation isn't an arbitrary design choice — it's the concrete implementation of foundational computer science principles.

The SOLID matrix:

  • Dependency Inversion Principle (DIP) — High-level business logic must not depend on low-level frameworks; both depend on abstractions. Hexagonal architecture enforces that all source-code dependencies point inward, toward the core.
  • Single Responsibility Principle (SRP) — Domain models gain a single reason to change: a change in business requirements. They're stripped of framework plumbing entirely.
  • Open/Closed Principle (OCP) — Open for extension, closed for modification. Need a new SMS provider? Write a new adapter. The core stays untouched.

Domain-Driven Design & component coupling:

Hexagonal isolation belongs to the same family as Clean Architecture (Robert C. Martin) and Onion Architecture (Jeffrey Palermo). It enforces the Acyclic Dependencies Principle (ADP), eliminating circular references, and honors the Stable Dependencies Principle (SDP) by making the most stable component — your business rules — the target of all dependencies.

4. The Engineering Payoff

Implementing hexagonal isolation means more boilerplate upfront, but the long-term ROI is substantial:

  • Bulletproof testability — Since the core only depends on abstract ports, you can mock databases and external APIs instantly. Test your entire business logic in milliseconds, no Docker containers or live servers required.
  • Framework agnosticism — Your framework becomes a delivery mechanism, not the definition of your application.
  • Future-proof evolution — When a faster database or cheaper cloud provider shows up, migration cost drops from a full rewrite to building a single new adapter.

5. See It in a Real Codebase: FlowBank

Reading about a pattern and watching it hold up under a real dependency graph are two different experiences. That's why this article ships with FlowBank — an open-source TypeScript banking API built to demonstrate hexagonal architecture through actual money-movement workflows: transfers, deposits, notifications, and payment processing.

src/
  core/          <- the hexagon's interior, zero framework imports allowed
    domain/         Account, Money, Transaction — pure business rules
    ports/           interfaces the core depends on, never concretes
    use-cases/       application logic, orchestrates domain + ports
    errors/          domain-specific error types

  adapters/       <- everything outside the hexagon
    postgres/        real DB implementation of the repository ports
    stripe/          real payment gateway implementation
    email/           real notification implementation
    http/            Express router — a driving adapter, calls IN to use cases
    in-memory/       fakes used by tests, swap-in for Postgres/Stripe/email

  main.ts         <- composition root: the ONLY file allowed to wire
                     concrete adapters into the use cases
Enter fullscreen mode Exit fullscreen mode

A couple of things worth actually trying rather than just reading about:

  • npm test runs the core's business-rule tests with no database, no network call, and no Docker container — just the in-memory adapters standing in for Postgres and Stripe.
  • Grep the codebase for proof, not promises: grep -rl "from.*adapters" src/core returns nothing. The dependency arrows in the diagram earlier in this article aren't aspirational — they're enforced by what actually got imported.
  • Try swapping StripePaymentGateway for a hypothetical PaystackPaymentGateway, or PostgresAccountRepository for a Mongo equivalent. Neither touches Account, Money, or the use cases — which is the entire argument of this article, made concrete.

Clone it, break it, extend it. The repo is the proof that this isn't just a diagram — it's a codebase that ships, tests fast, and survives a provider swap.

Conclusion

Technical debt doesn't usually arrive as one bad decision — it accumulates one framework shortcut at a time, until the business logic and the delivery mechanism are so entangled that neither can change without the other breaking. Hexagonal isolation is a direct, structural answer to that: push every framework, database, and vendor SDK to the edges, and keep a small, dependency-free core that only knows about the business it serves.

The payoff isn't theoretical. It shows up as tests that run in milliseconds instead of minutes, migrations that take a sprint instead of a quarter, and a codebase new engineers can actually reason about because the business rules aren't buried inside ORM hooks and route handlers.

Stop building software like a 15-minute tutorial. Isolate your core, protect your boundaries, clone FlowBank and see the pattern for yourself, and claw back your engineering budget.

#HappyCoding

#BuildScalableSystems

Top comments (0)