intermediate 2 min answer Multiple choice

What does hexagonal architecture give you that a conventional layered design does not?

hexagonalports-adapterstestingsymmetryboundaries
Pick one
Show the full answer Hide the answer

What is being tested

Whether you can name the specific structural difference rather than treating the two as synonyms.

The distinguishing property

Layered architecture has a privileged direction: requests flow down from presentation to persistence. The web layer is conceptually different from the database layer.

Hexagonal treats them identically. The core defines ports — interfaces in its own language — and everything outside is an adapter. An HTTP handler is a driving adapter; a database client is a driven adapter; a test harness is just another adapter.

What that symmetry actually enables

Testing the whole application with in-memory adapters. Not unit tests of individual classes — full use-case tests exercising real business logic against fake infrastructure, running in milliseconds.

This is the benefit that changes team behaviour most. A team whose entire business-logic suite runs in two seconds behaves differently from one that needs a database and forty seconds: they run it more, they trust it more, and they refactor more willingly. That compounds over years.

Adding a delivery mechanism cheaply. A service with an HTTP API gains a queue consumer by writing one adapter, with no change to the core. In a layered design the entry point is structurally special and this is more invasive.

The core owns the contract. A provider's peculiar interface is translated at the boundary rather than shaping the domain — the same instinct as an anti-corruption layer.

The rule that keeps it honest

The port is defined in the core's language, not the adapter's. A port called saveToPostgres(entity) has already failed. It is orders.save(order), and the adapter knows about PostgreSQL.

The same applies to failure semantics: the core should see a domain-level failure, not a driver exception, or the coupling has simply relocated into the error handling.

The failure modes

  • Ports shaped by the adapter, so swapping is still impossible and you have paid for nothing.
  • An adapter per class rather than per external concern, multiplying files.
  • In-memory test adapters diverging from the real ones, so tests pass and production fails. Run a shared contract test suite against both implementations.
  • Applied uniformly across services where half of them are CRUD and do not need it.

When it is not worth it

The same answer as clean architecture: a small CRUD service with one delivery mechanism gets ceremony and no benefit. The strongest single justification in practice is fast realistic tests, so if the test suite is already fast and the domain is thin, the case is weak.