Skip to main content

Hexagonal Architecture

Ports and adapters pattern

The Power Outlet Analogy

Power outlets are standardized ports:

ComponentRole
Outlet (Port)Standardized interface
Device (Adapter)Plugs into the interface
Power sourceHidden behind the outlet
  • Any device can plug in
  • Outlet doesn't care what's connected
  • Device doesn't care how electricity is generated

Hexagonal architecture works the same way. Your business logic (core) connects to the outside world through standardized ports.


What Is Hexagonal Architecture?

Also called "Ports and Adapters"

ConceptDescription
CoreBusiness logic at the center
PortsInterfaces defined by core
AdaptersImplementations that plug into ports
Key ruleCore doesn't know about databases, web, etc.

The Hexagon Shape

           ┌─────────────────────────────┐
           │         Web UI              │  Adapter
           └──────────┬──────────────────┘
                      │ Port
           ┌──────────▼──────────────────┐
           │                             │
 REST API ─┤      Business Logic         ├─ Database
           │       (Domain Core)         │
 CLI ──────┤                             ├─ Email API
           └──────────▲──────────────────┘
                      │ Port
           ┌──────────┴──────────────────┐
           │       Message Queue         │  Adapter
           └─────────────────────────────┘

Multiple sides for different connections.

Ports and Adapters

Ports

Ports are interfaces defined by the core:

Example PortWhat It Declares
UserRepository"I need a way to save users"
NotificationService"I need a way to send notifications"
PaymentGateway"I need a way to charge cards"

Ports don't know HOW - just WHAT is needed.

Adapters

Adapters are implementations that plug into ports:

PortAdapter AAdapter B
UserRepositoryPostgresUserRepoInMemoryUserRepo
NotificationServiceSendGridAdapterMockEmailAdapter
PaymentGatewayStripeAdapterTestPaymentAdapter

Multiple adapters can implement the same port!


Primary vs Secondary Ports

Primary Ports (Driving)

Drive the application - they call into the core.

ExamplesThey say...
Web controller"User wants to place an order"
CLI commands"Admin wants to reset password"
Message handlers"New message arrived"
Scheduled jobs"Time to run daily report"

Secondary Ports (Driven)

Driven by the application - core calls them.

ExamplesCore says...
Database"Save this order"
External APIs"Verify this address"
Email service"Send confirmation email"
File storage"Store this document"

Visualization

                  DRIVING (calls INTO core)
                         │
                         ▼
          ┌──────────────────────────┐
          │                          │
Controller┤                          ├─→ Database
          │      DOMAIN CORE         │     DRIVEN
CLI ─────→┤                          ├─→ Email
          │                          │     DRIVEN
          └──────────────────────────┘
                         ▲
                         │
                  DRIVING (calls INTO core)

Why Hexagonal Architecture?

1. Testability

Without HexagonalWith Hexagonal
Test needs databasePlug in fake adapter
Test needs web serverPlug in fake adapter
Slow, flaky testsFast, reliable tests

2. Flexibility

Change ThisImpact
Database (PostgreSQL → MongoDB)Only change adapter
REST → GraphQLAdd GraphQL adapter
SendGrid → MailgunOnly change adapter

Core unchanged!

3. Clear Boundaries

RuleEffect
No database code in domainBusiness logic isolated
No HTTP in business rulesEasy to understand
Clear separationEasy to navigate codebase

4. Technology Independence

Core knows nothing about frameworks, databases, or UI technology. Framework changes? Core stays the same.


Project Structure

src/
├── domain/           ← Core business logic
│   ├── models/
│   ├── services/
│   └── repositories/ ← Port interfaces
│
├── application/      ← Use cases
│   └── use_cases/
│
├── adapters/         ← Implementations
│   ├── web/          ← REST controller
│   ├── cli/          ← Command line
│   ├── persistence/  ← Database
│   └── external/     ← Third-party APIs
│
└── main.py           ← Wire everything together

Dependency Rule

Core Must Not Know About Adapters

DirectionValid?
Adapters → Application → Domain✅ Yes
Domain → Adapters❌ No!
Application → Adapters❌ No!

Dependencies point INWARD only. This protects your core from external changes.

How to Enforce

  1. Ports (interfaces) defined in domain/application
  2. Adapters implement those interfaces
  3. Dependency injection connects them at runtime

Hexagonal vs Clean Architecture

ArchitectureEmphasis
HexagonalPorts and adapters metaphor
Clean ArchitectureLayers (entities, use cases, etc.)

Similarities:

  • Both isolate domain from infrastructure
  • Both depend inward
  • Mostly a difference in terminology

Common Mistakes

1. Domain Depends on Framework

❌ Bad✅ Good
Entity has @Column ORM decoratorEntity is pure Python/TypeScript
Domain imports framework typesAdapter handles mapping

2. Leaky Abstractions

❌ Bad✅ Good
Port returns SQLAlchemyUserPort returns User (domain type)
Database specifics leakPort is database-agnostic

3. Too Many Ports

❌ Bad✅ Good
GetUserPort, SaveUserPort, DeleteUserPortUserRepository (cohesive)
One port per operationGroup related operations

When to Use

Good Fit

ScenarioWhy It Helps
Long-lived applicationsWorth the investment
Complex business logicCore protected
Need high testabilityEasy to mock
May change infrastructureSwap adapters
Multiple entry pointsSame core, different adapters

Might Be Overkill

ScenarioWhy
Simple CRUD appsToo much structure
Short-lived prototypesSpeed matters more
Very small codebasesOverhead not worth it

FAQ

Q: Is hexagonal the same as clean architecture?

Very similar - both isolate domain from infrastructure. Terminology differs slightly.

Q: How do I start with an existing app?

Gradually extract domain logic. Introduce ports for one area at a time.

Q: Does this add complexity?

Initially, yes. Pays off as app grows and changes.

Q: What language works best?

Any language with interfaces/protocols. Common in Java, C#, TypeScript, Python.


Summary

Hexagonal architecture isolates business logic from external concerns through ports (interfaces) and adapters (implementations).

Key Takeaways:

  • Core business logic at the center
  • Ports define interfaces needed by core
  • Adapters implement those interfaces
  • Dependencies point inward
  • Core doesn't know about databases, web, etc.
  • Easy to test with fake adapters
  • Primary ports drive, secondary ports are driven

Think of your app as having a pluggable architecture!

Leave a Comment

Comments (0)

Be the first to comment on this concept.

Comments are approved automatically.