DEV Community

Cover image for The Project That Finally Taught Me Hexagonal Architecture
Barış Saylı
Barış Saylı

Posted on

The Project That Finally Taught Me Hexagonal Architecture

Most articles about Hexagonal Architecture show clean diagrams and confident explanations.

This is not one of those articles.

This is a story about what happened when I tried to apply Hexagonal Architecture to a real codebase — and how the project forced me to admit a simple truth:

I didn’t actually understand it yet.


What the project was (in one minute)

I was building Codegen Blueprint: a CLI-driven Spring Boot project generator.

Not the “fastest starter template” kind.

The goal was different:

  • generate a Spring Boot project with a clear architecture shape from day zero
  • and optionally generate build-time architecture guardrails (ArchUnit tests) so boundaries stay enforceable via mvn verify

In short:

A generator that cares less about day-one speed and more about how well the architecture survives over time.

That’s the context.

Now here’s what went wrong.


Phase 1 — I had the vocabulary, not the understanding

Like many developers, I thought I understood Hexagonal Architecture.

Not because I had practiced it deeply — but because I had:

  • read blog posts
  • watched talks
  • seen the same clean diagrams a hundred times
  • even used AI to reason about the concepts

At some point I said:

“Okay, I get it.”

I didn’t.

I created the familiar folders:

domain
application
adapter
Enter fullscreen mode Exit fullscreen mode

…and expected clarity to emerge.

It didn’t.

What actually happened

  • I couldn’t clearly explain what a port was without slipping into implementation details
  • “domain” became a place where I quietly parked technical decisions
  • adapters turned into a messy blend of IO, orchestration, and business logic

What I built wasn’t hexagonal architecture.

It was spaghetti with hexagonal labels.

That was the first painful lesson:

Hexagonal architecture is unforgiving.
If your thinking is unclear, the code exposes it immediately.


Phase 2 — The project asked a question my architecture couldn’t answer

Here’s the part that matters: I wasn’t building a “one-off app”.

Even in the early days, I knew the generator would eventually need to evolve.

Not because I wanted to chase trends — but because generators live or die by extensibility.

So a question started to appear in my head, slowly at first:

“If this tool survives, how will it change without rewriting its core?”

At that moment, I wasn’t thinking in terms of a long list like
“frameworks, languages, build tools”.

I was thinking about something more concrete:

today: Spring Boot + Maven + Java

tomorrow: the same core, but a different layout or delivery surface

later: maybe a new adapter (CLI today, REST tomorrow)

maybe: Gradle, Kotlin — or even another framework — once the core is strong enough

The details weren’t the point.

The point was:

Change was inevitable.
And my current design made every change feel dangerous.

That’s when Hexagonal Architecture stopped being a diagram.

It became a survival requirement:

If the core isn’t clean, evolution turns into rewrite.


Phase 3 — The reset

I tried to refactor my way out.

I renamed packages.

I moved code around.

I “cleaned up” classes.

But it wasn’t getting better — because the real problem was the mental model.

So I did something I almost never do.

I deleted everything.

Not refactored.

Not reorganized.

Deleted.

Then I restarted with a single non-negotiable rule:

The domain must remain stable even if everything else changes.

That rule became my compass.


Phase 4 — What Hexagonal Architecture finally meant (for me)

Once I rebuilt around that rule, the layers started to mean something.

Domain — Pure and intentionally boring

The domain described the generator’s core concepts and constraints.

No Spring.

No filesystem.

No templating.

Just decisions that should survive.

Application — Orchestration only

Use cases coordinated the workflow:

  • what needs to be produced
  • in which order
  • under which options

Application depended on ports, not implementations.

Ports — “what I need”, not “how it’s done”

Ports became the language of the core:

  • “render templates”
  • “produce build configuration”
  • “write generated output”

Not “use Mustache” or “write to disk like this”.

Adapters — where the world (and chaos) lives

Adapters held the replaceable things:

  • Maven POM generation
  • wrappers
  • layout templates
  • README output
  • filesystem writing

The architecture started to feel honest.

And a second big lesson clicked:

Hexagonal architecture isn’t about folder names.
It’s about isolating decisions.


Phase 5 — The surprising payoff

Once boundaries stabilized, something unexpected happened.

Testing became easier.

  • domain tests became simple and fast
  • application tests mocked ports cleanly
  • integration tests focused on real pipelines instead of tangled internals

But then a different kind of anxiety showed up.

Even with good tests, architecture can still drift silently.

A project can “work”… while its structure gets worse.

So I asked a new question:

“What makes architectural mistakes visible immediately?”


Phase 6 — Turning architecture into feedback (guardrails)

Here’s the missing concept many people don’t have until they live it:

Business tests validate behavior.

But they don’t necessarily validate structure.

A controller can start calling a repository directly.

The app still works.

The tests still pass.

Yet the architectural intent is already compromised.

That’s where architecture guardrails come in.

What I mean by “guardrails”

In Codegen Blueprint, guardrails are:

generated ArchUnit tests (JUnit + ArchUnit) that run at build time during mvn verify.

When a boundary is violated, the build fails deterministically.

No runtime checks.

No “someone notices in review”.

Just an immediate, non-negotiable signal.

Example shape:

mvn verify
Enter fullscreen mode Exit fullscreen mode

…and you see something like:

Architecture Violation

Adapters must not depend on application implementations
Expected: adapter → application.port
Found:    adapter → application.usecase

BUILD FAILED
Enter fullscreen mode Exit fullscreen mode

Nothing started.

No app was executed.

The build evaluated the architectural contract.

That changed the feedback loop completely.


Phase 7 — The moment it felt “real”

The real psychological shift wasn’t “guardrails are cool”.

It was this:

Guardrails weren’t protecting the code from juniors.
They were protecting me from invisible drift.

Even as the person who designed the boundaries.

Even as the person who “knew” the architecture.

Under continuous change, humans are not reliable architecture validators.

Build-time feedback is.


What this project taught me

Hexagonal Architecture is not about:

  • diagrams
  • folder structures
  • academic purity

It is about:

  • protecting your domain
  • isolating technology decisions
  • making evolution possible without rewrite
  • confronting unclear thinking early

The hard part is not the pattern.

The hard part is the discipline.


Closing thought

You rarely understand Hexagonal Architecture before applying it.

You apply it.

You struggle.

You fail.

You rebuild.

Eventually the boundaries start to mean something.


If you're curious about the project that forced me to learn these lessons the hard way:

👉 https://github.com/blueprint-platform/codegen-blueprint

Top comments (0)