
The feedback loop that wasn't
Twenty minutes. That's how long one team waited between pushing a branch and finding out whether their code worked. Push, walk away, come back to either green or a suspiciously red flake. At that cadence, CI isn't a feedback loop — it's a coffee break with anxiety attached.
What fixed it wasn't a faster machine, a smarter test parallelization strategy, or a new CI vendor. It was rethinking what the tests were actually coupled to. The answer turned out to be hexagonal architecture — and the 75% reduction in CI time (20 minutes down to 5) is a measurable signal that the approach works.
<> "Your CI is no longer a feedback loop when it's a ceremony. Slow tests don't just waste time — they hide regressions behind flakiness and train developers to stop trusting the suite."/>
What hexagonal architecture actually means in practice
The pattern goes by several names — ports and adapters, clean architecture — but the core idea is simple: your business logic should not know or care whether it's talking to a PostgreSQL database, a REST API, or a test double. The domain defines interfaces (ports) for what it needs, and concrete implementations (adapters) live at the edges.
Here's what that looks like in TypeScript:
1// Port — defined by the domain, owned by the domain
2interface OrderRepository {
3 findById(id: string): Promise<Order | null>;
4 save(order: Order): Promise<void>;
5}
6
7// Domain service — depends only on the interface
8class OrderService {Notice what the test version enables: you can exercise cancelOrder — including all its business rules, edge cases, and error paths — without spinning up a database, seeding fixtures, or waiting for network roundtrips. The test runs in milliseconds. Multiply that across hundreds of domain behaviors and you start to see where 15 minutes went.
Why CI gets slow in the first place
Most CI slowdowns aren't caused by individual slow tests. They're caused by architectural coupling that forces broad, expensive tests to cover things that could be verified cheaply.
When business logic is tangled with infrastructure — database queries embedded in service methods, HTTP client calls inside domain objects, third-party SDKs imported directly into core modules — you can't test the logic without bringing the whole stack. Every test becomes an integration test. Every integration test needs real or mocked infrastructure. Mocking at scale is its own maintenance burden, and real infrastructure in CI introduces flakiness, cold-start latency, and hidden coupling between test suites.
Hexagonal architecture breaks this pattern structurally. When the domain genuinely doesn't depend on infrastructure, your fast tests don't need to pretend it doesn't exist — it just doesn't exist from the domain's perspective.
The test pyramid actually works when architecture supports it
The classic test pyramid says: many unit tests, some integration tests, few end-to-end tests. Most teams pay lip service to this while running a test suite that's actually an inverted pyramid — a thin layer of unit tests on top of a massive base of slow integration and E2E checks.
Hexagonal architecture makes the pyramid structurally enforceable:
- Domain tests (the wide base): pure logic, in-memory adapters, no infrastructure. These should be the overwhelming majority and run in seconds.
- Adapter tests (the middle): verify that your PostgresOrderRepository actually maps rows to domain objects correctly. These need a real database but are narrow — they test mapping, not business rules.
- Contract tests (selective): when you depend on third-party APIs, test the contract at the boundary rather than dragging that dependency into every test run.
- E2E tests (the narrow top): critical user journeys only. Not every feature, not every edge case.
This structure is possible without hexagonal architecture, but hexagonal architecture makes it natural. The seams are already there; you just plug in the right adapter for each test tier.
What the 75% reduction actually tells you
A 20-to-5 minute improvement isn't just a productivity number. It's a signal about coupling. The fact that the same test coverage can run in a quarter of the time after introducing proper architectural boundaries means the original suite was doing enormous amounts of unnecessary infrastructure work.
That work had compounding costs:
- Flakiness — infrastructure-dependent tests fail intermittently, which erodes trust in the suite and trains developers to ignore red builds
- Context switching — waiting 20 minutes means switching tasks, losing context, and paying the re-engagement tax on return
- Slower iteration — long feedback loops push developers toward larger commits, which makes failures harder to diagnose
- Hidden regressions — when CI is slow and flaky, real bugs hide behind the noise
Five-minute CI changes the behavior of the whole team. Developers run CI before merging without hesitation. Red builds get fixed immediately because the context is still fresh. Smaller commits become the norm because the loop is tight enough to support them.
One caution worth taking seriously
Hexagonal architecture adds indirection. Ports, adapters, dependency injection wiring, factory functions — it's real complexity that has to be maintained. For a straightforward CRUD app with minimal business logic, this overhead may not be justified. The pattern earns its keep when you have meaningful domain behavior that changes independently of infrastructure decisions.
The honest test: if most of your logic is "validate this field, write this row, return this response," the ports are probably wrapping nothing. If you have real business rules — pricing calculations, state machines, workflow enforcement, complex validation — hexagonal architecture gives those rules a home where they can be tested and changed without touching a database.
Where to start if your CI is slow
Measure first. Track CI duration, count flaky test failures per week, and estimate what percentage of your tests actually require live infrastructure. These numbers tell you where the pain is before you start refactoring.
Find one domain concept with no natural home and give it one — a class with a clear interface, no infrastructure imports, no framework dependencies. Write fast tests for it. See how it feels.
Push protocol details to the edges. HTTP status codes, SQL queries, message broker schemas — none of these belong in business logic. Move them out incrementally. Each migration makes the core a little more testable.
Don't refactor the whole codebase at once. Hexagonal architecture is most effective when introduced gradually at the boundaries that hurt most. Start with whatever your slowest, flakiest tests are covering, and work inward from there.
Faster CI is a symptom of better design. The 15 minutes you save are real, but the underlying benefit is a codebase where business rules can be understood, tested, and changed without wading through infrastructure. That's the insight worth carrying forward.

