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
…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
…and you see something like:
Architecture Violation
Adapters must not depend on application implementations
Expected: adapter → application.port
Found: adapter → application.usecase
BUILD FAILED
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:
Top comments (0)