Skip to main content

Hexagonal Architecture (Ports and Adapters)

Hexagonal Architecture isolates business logic behind ports, with adapters connecting databases, UIs, and messaging, so the core stays independent of any technology. You can swap infrastructure and test the core in isolation without touching business rules.

Organization
Alistair Cockburn
Published
Jun 4, 2005

Best Practice: Hexagonal Architecture (Ports and Adapters)

Hexagonal Architecture, also called Ports and Adapters, keeps the core business logic of an application completely independent of the technologies around it. The core defines ports, which are interfaces describing what it needs and offers. Adapters implement those ports to connect to the outside world: a database adapter, a REST adapter, a message-queue adapter. Because the core depends only on its own ports and never on concrete infrastructure, you can swap databases, UIs, or messaging without touching business rules, and you can test the core in isolation. Alistair Cockburn introduced the pattern in 2005. It matters because mixing business logic with framework and infrastructure code makes systems brittle and hard to change.

Step-by-Step Implementation Guidance

  1. Isolate the domain: pure business logic with no framework or infrastructure imports.
  2. Define driving ports for inbound use cases the application offers.
  3. Define driven ports for outbound needs such as persistence or messaging.
  4. Implement adapters that translate between external technology and the ports.
  5. Wire dependencies so the core depends on port interfaces, not adapters.
  6. Use dependency injection to supply adapters at the edges.
  7. Test the core against in-memory adapters, with separate integration tests for real ones.

Common Mistakes Teams Make When Ignoring This Practice

  • Letting framework or ORM types leak into business logic.
  • Pointing the core's dependencies at concrete infrastructure instead of interfaces.
  • Putting business rules inside controllers or repositories.
  • Skipping in-memory adapters, making the core slow and hard to test.
  • Creating so many layers that simple changes touch many files for no benefit.

Tools and Techniques That Support This Practice

  • Dependency injection frameworks such as Spring or NestJS.
  • Interface-based design in the language of choice.
  • In-memory fakes and mocks for testing the core.
  • Domain-Driven Design as a complementary modeling approach.

How This Practice Applies to Different Migration Types

  • Cloud Migration: Replace an infrastructure adapter to target a cloud service without changing the core.
  • Database Migration: Swap the persistence adapter to move to a new datastore, leaving business rules intact.
  • SaaS Migration: Write a new adapter to integrate a SaaS provider behind the same port.
  • Codebase Migration: Use ports as clean seams when extracting modules from a monolith.

Checklist

  • Keep the domain free of framework and infrastructure code.
  • Define driving ports for inbound use cases.
  • Define driven ports for outbound dependencies.
  • Implement adapters that translate to external tech.
  • Point all core dependencies at interfaces.
  • Inject adapters at the application edges.
  • Test the core with in-memory adapters.