It works. Until it doesn't.

As systems scale, teams grow, and complexity creeps in, more senior engineers are switching to something else:

Hexagonal Architecture, also known as Ports and Adapters.

It's not a trend. It's a practical shift born from real pain in production systems.

Let's walk through why.

The Classic Layered Architecture

This is the pattern many of us were taught:

Controller
   ↓
Service (Business Logic)
   ↓
Repository (DB Access)
   ↓
Database

It's easy to set up. It separates concerns. But once real-world complexity hits — background jobs, message queues, third-party APIs, testability — the cracks show.

Where Layered Starts to Break

1. Directional Coupling

Layers are tightly coupled downwards. The service layer must know exactly how the repository works. Testing often requires wiring up every layer below.

2. Hard to Replace Infra

Want to switch from Mongo to Redis for one use case? Good luck — you'll be threading that logic through service and controller layers.

3. Tests Depend on Infra

You either:

  • Mock the entire DB layer (not ideal), or
  • Spin up real databases in tests (slow and brittle)

4. Business Logic Gets Polluted

In real codebases, business logic ends up mixed with logging, DB calls, HTTP clients, Kafka producers — even retries.

What Hexagonal Architecture Does Differently

Hexagonal says: invert the direction.

Instead of application code depending on infrastructure (DB, APIs), the infrastructure depends on the application.

Business logic lives at the core — pure and testable.

Here's how it looks:

           ┌────────────────────┐
           │    HTTP Handler    │
           │    CLI Command     │
           │    Kafka Consumer  │
           └────────┬───────────┘
                    │
            ┌───────▼────────┐
            │  Application   │
            │  (Use Cases)   │
            └───────┬────────┘
                    │
            ┌───────▼────────┐
            │   Domain       │
            │ (Interfaces)   │
            └───────┬────────┘
                    │
      ┌─────────────▼────────────┐
      │   Infrastructure Layer   │
      │ (DB, Redis, APIs, etc.)  │
      └──────────────────────────┘

Folder Structure in Go

payment-service/
│
├── cmd/                 # main.go entrypoint
├── internal/
│   ├── app/             # Use cases
│   ├── domain/          # Entities + Interfaces
│   ├── adapters/        # HTTP, Kafka, Mongo, etc.
│   └── config/          # Env loading
└── go.mod

Each adapter implements an interface from the domain.

Real-World Example: Hexagonal in Go

Interface in Domain

// domain/payment_repository.go
type PaymentRepository interface {
    Save(payment *Payment) error
    FindByID(id string) (*Payment, error)
}

Pure Use Case Logic

// app/payment_service.go
type PaymentService struct {
    repo domain.PaymentRepository
}

func (s *PaymentService) Process(payment *domain.Payment) error {
    if payment.Amount <= 0 {
        return errors.New("invalid amount")
    }
    return s.repo.Save(payment)
}

Mongo Adapter

// adapters/mongo/payment_repo.go
type MongoRepo struct {
    collection *mongo.Collection
}

func (m *MongoRepo) Save(p *domain.Payment) error {
    _, err := m.collection.InsertOne(context.TODO(), p)
    return err
}

Testing Becomes Stupidly Simple

Just fake the interface:

type FakeRepo struct {
    saved []*domain.Payment
}

func (f *FakeRepo) Save(p *domain.Payment) error {
    f.saved = append(f.saved, p)
    return nil
}

Now you can write unit tests that test only your logic — no database, no HTTP.

Hexagonal Also Supports More Than Just HTTP

You can wire the same business logic to:

  • HTTP API
  • gRPC service
  • Kafka consumer
  • CLI tool
  • Batch job

All without changing your core logic.

That's massive for microservices where jobs, consumers, and APIs are everywhere.

When Should You Use Hexagonal?

You're working with microservices

You want clean test boundaries

You want to avoid logic duplication

You want to swap DBs or message queues easily

You need to support multiple input/output formats

If you're building a CRUD-only service with no scaling, sure — layered might still be okay.

But once real logic comes in — go hexagonal.

Final Thoughts

Senior engineers don't move away from Layered Architecture because it's wrong. They move because it stops working at scale.

Hexagonal Architecture gives you:

  • Clean core logic
  • Testable units
  • Replaceable infrastructure
  • Better long-term maintainability

It's not hype. It's not new. But it works — and that's why more and more experienced Go developers are adopting it.