Hexagonal Architecture Pitfalls & Trade-offs

You slapped hexagonal architecture on your CRUD app, chasing clean code. Now a simple field rename touches six files. Reality bites.

Hexagonal Architecture's Dirty Secret: Five Files for One Field Change — theAIcatchup

Key Takeaways

  • Hexagonal architecture adds massive overhead for simple changes like adding a field, touching 5+ files.
  • Skip it for empty domains, single adapters, early prototypes, or perf-critical paths.
  • It shines only in stable, logic-rich domains with real adapter swaps and large teams.

Hexagonal architecture. That’s the buzzword devs chased for years — ports, adapters, domain purity, all wired through a tidy composition root. Everyone expected it to banish spaghetti code forever, make tests a breeze, swap databases without a sweat. But here’s the shift: in the trenches of real apps, it often turns into a multiplier for pain, especially CRUD slogfests where you’re just shuttling data around.

Look, this isn’t some anti-pattern screed. The pattern works wonders when your domain’s got teeth — complex rules, multiple UIs, flaky external APIs. Yet for most projects? It’s like building a Mars rover to fetch groceries.

That Brutal Upfront Tax

Adding a promo_code to your Order model. Flat codebase: tweak the Pydantic schema, update the SQL query, done in 10 minutes. Hexagonal setup?

Cascade city.

First, domain/models.py gets the new field. Then ports.py — if your repository needs a find_by_promo_code, that’s a new method in the Protocol. SQLAlchemy adapter chases it, adding the column map and query. In-memory test repo mirrors it. ORM table schema expands. Boom — five files, minimum.

Five files for one field. In a small FastAPI app with a Pydantic model and a SQLAlchemy model, that’s two files.

That’s the original gripe hitting home. When you’re prototyping, nailing down requirements, this rigidity slows you to a crawl. You’re not protecting invariants; you’re just paying ceremony tax.

And juniors? Forget it. They stare at api/orders.py calling OrderService, which invokes a Protocol. Where’s the SQL? Hunt through dependencies.py for the wiring. Debugging jumps three abstraction layers before the real error in SQLAlchemy bubbles up.

Ports lock you in early. Great for stable domains. Hell for discovery phase, where every sprint rewrites interfaces — and every adapter.

One field. Six touches. Multiply by 20 features in MVP. You’ve optimized for yesterday’s problem.

Why Does Tracing Code in Hexagonal Apps Feel Like a Maze?

Indirection’s the killer. Sure, it decouples. But follow a request: HTTP handler -> service -> port -> (squint) which adapter? Composition root hides the concrete impl. Stack traces? Layered nonsense until the ORM spits truth.

Here’s my unique angle, absent from the source: this echoes the EJB bloat of early 2000s Java enterprise. Everyone layered POJOs behind facades, session beans, entity beans for ‘portability.’ Result? Config hell, performance drags, devs fleeing to Rails. Hexagonal’s the modern remix — noble intent, but overkill breeds the same fragility.

Flat codebases let you grep ‘promo_code’ and fix everything. Hex lets you grep and still wonder: did I miss the test double?

Performance whispers too. Hot paths — think order processing loops — pay for virtual dispatch on every repo call. Not catastrophic, but in pipelines crunching millions, it adds up. Why abstract what you’ll never swap?

Signal 1: Your Domain’s Just a glorified DTO

No logic? Ditch hex.

BlogPost dataclass with title, body, id. Pure data bag. No validate(), no computeTotal(). You’ve fenced an empty yard.

Admin panels, ETL jobs, internal tools — they’re CRUD zoos. Hexagonal adds zero value, pure drag.

When’s a Single Adapter a Red Flag?

Ports shine with swaps: Postgres today, Dynamo tomorrow. But solo impl? You’ve prepaid for ghosts.

UserRepository Protocol -> only PostgreSQLUserRepository. Forever. Test via test DB, inline the SQL in service. Half the code, same reliability.

It’s hype spin — ‘flexible architecture!’ Nah. Flexibility unused is waste.

Early projects scream no. Domain wobbles? Flat first, ports emerge when pain hits: second adapter needed, or test mocks brittle.

That’s not lazy. It’s pragmatic. Refactor boundaries once clear.

But Wait — When DOES Hexagonal Pay Off?

Stable domains with behavior. E-commerce core: Order aggregates enforce invariants (can’t ship unpaid), multiple adapters (SQL prod, Kafka events, in-mem tests). Services pure, testable sans DB.

Multi-client apps — web API, mobile, CLI — sharing domain. Or regulated spaces: audit every persistence swap.

There, the tax compounds value. Isolation lets you mock externalities, evolve adapters independently.

Miss those signals? You’re building castles on sand.

Why Skip Hexagonal in the AI Prototyping Era?

Bold prediction: with LLMs spitting code, early-stage velocity trumps architecture purity. Flat prototypes validate ideas fast; hex slows iteration. We’ll see more ‘prototyping flat, harden later’ as norm — especially solo devs or tiny teams racing MVPs.

Corporate PR pushes patterns as defaults. Skeptical eye: it’s often job security via complexity, not user value.

Trade-offs real. Weigh ‘em.


🧬 Related Insights

Frequently Asked Questions

What is hexagonal architecture?

Ports-and-adapters pattern isolating domain from tech details — pure core, swappable outsides.

When should I avoid hexagonal architecture?

Skip for simple CRUD, single adapters, discovery-phase apps, or perf-critical paths. Use flat structures.

Does hexagonal architecture improve testability always?

Only if domain’s rich and you swap adapters. Otherwise, test DBs suffice cheaper.

Is hexagonal architecture overkill for FastAPI apps?

Often yes — especially CRUD-heavy ones. Start flat, refactor if boundaries blur.

Priya Sundaram
Written by

Hardware and infrastructure reporter. Tracks GPU wars, chip design, and the compute economy.

Frequently asked questions

What is hexagonal architecture?
Ports-and-adapters pattern isolating domain from tech details — pure core, swappable outsides.
When should I avoid hexagonal architecture?
Skip for simple CRUD, single adapters, discovery-phase apps, or perf-critical paths. Use flat structures.
Does hexagonal architecture improve testability always?
Only if domain's rich and you swap adapters. Otherwise, test DBs suffice cheaper.
Is hexagonal architecture overkill for FastAPI apps?
Often yes — especially CRUD-heavy ones. Start flat, refactor if boundaries blur.

Worth sharing?

Get the best AI stories of the week in your inbox — no noise, no spam.

Originally reported by Dev.to

Stay in the loop

The week's most important stories from theAIcatchup, delivered once a week.