Blog / Architecture
Architecture

Hexagonal Architecture in Practice: Beyond the Theory

Practical implementation of ports and adapters architecture with real code examples, dependency injection, testing strategies, and where teams over-engineer it.

Akhil Sharma

Akhil Sharma

January 7, 2026

10 min read

Hexagonal Architecture in Practice: Beyond the Theory

Hexagonal architecture (ports and adapters) has been around since 2005, but most articles explain the theory without showing what it actually looks like in a production codebase. The concept is simple: your business logic sits in the center, and all external interactions happen through well-defined interfaces. The implementation is where teams get confused — or over-engineer themselves into a framework that's harder to work with than the monolith they were trying to escape.

The Core Idea

Driving (input) adapters call into your application: HTTP handlers, gRPC servers, CLI commands, message consumers.

Driven (output) adapters are called by your application: database repositories, cache clients, message producers, external API clients.

Ports are the interfaces between the domain and adapters. Input ports define what the application can do. Output ports define what the application needs.

Project Structure

Here's what a hexagonal Go service looks like on disk:

The dependency rule: code in domain/ never imports from adapters/. Adapters import from ports/ and domain/. The cmd/ package wires everything together.

Defining Ports

Ports are interfaces. Input ports describe use cases. Output ports describe infrastructure needs.

go
go

Key principle: ports are defined by the domain, not by the adapters. The OrderRepository interface has Save and FindByID because those are what the domain needs — not because PostgreSQL supports those operations. If you find your ports reflecting database-specific operations (like ExecuteQuery), you've inverted the dependency.

Domain Logic

Advanced System Design Cohort

We build this end-to-end in the cohort.

Live sessions, real systems, your questions answered in real time. Next cohort starts 2nd July 2026 — 20 seats.

Reserve your spot →

The domain service implements the input port using output ports. It contains business rules and orchestration logic. No framework imports, no database drivers, no HTTP libraries.

go

Notice: no SQL, no Redis commands, no HTTP calls. The domain service orchestrates business logic through interfaces. This is the payoff — the business logic is testable without any infrastructure.

Adapters: Implementing Ports

Driven Adapter: PostgreSQL Repository

go

Driving Adapter: HTTP Handler

go

Testing: The Real Payoff

The biggest benefit of hexagonal architecture is testability. Domain logic is tested with mock adapters — no database, no network, no containers.

go

These tests run in milliseconds, require no setup, and cover business logic exhaustively. Integration tests for adapters run separately, testing that the PostgreSQL adapter correctly executes SQL and the Kafka adapter correctly produces messages.

Wiring: Where It All Comes Together

go

No DI framework. No reflection magic. Just constructor injection. This is readable, debuggable, and obvious.

Where Teams Over-Engineer It

Too many layers. Some teams add an "application service" layer between ports and domain, plus DTOs at every boundary, plus separate request/response objects per adapter. For a service with 5 endpoints, you end up with 40 files. Keep it simple: ports, domain, adapters. Add layers only when complexity demands it.

Premature abstraction. If you have one database and will only ever have one database, wrapping it in a repository interface still has value (testing), but don't build a generic DataStore interface that abstracts over SQL, NoSQL, and file storage "just in case."

Port explosion. One port per method is overkill. Group related operations into cohesive interfaces. An OrderRepository with 4-5 methods is fine. An OrderSaver, OrderFinder, OrderUpdater, and OrderDeleter with one method each is interface bloat.

Ignoring the simple case. If your service has no business logic — it's a CRUD wrapper around a database — hexagonal architecture adds complexity without adding value. Use it when you have business rules worth protecting. Skip it for simple data access services.

Hexagonal architecture works best when treated as a dependency management strategy, not a religion. The core rule — domain logic depends on abstractions, not implementations — is what matters. The folder structure, naming conventions, and layer count are preferences, not requirements.

Hexagonal Architecture Ports and Adapters Clean Code Go

become an engineering leader

Advanced System Design Cohort