A learning path ready to make your own.

Hexagonal Architecture

Hexagonal Architecture — Concise Summary Hexagonal Architecture (Ports & Adapters) is an architecture style that isolates the application core (domain and use cases) from infrastructure and delivery mechanisms (UI, DB, messaging, third‑party services). Its goal is to keep business logic independent, testable, and replaceable while making integration explicit via well‑defined interfaces. Why use it Easier unit testing of domain/use‑case code without infrastructure. Replaceable infrastructure (swap DBs, UIs, queues) with minimal core changes. Clear boundaries and better modularity; explicit interaction points (ports) and implementations (adapters). History Proposed by Alistair Cockburn (circa 2005–2006). The hexagon is a metaphor: the core sits in the center and faces represent ports. Influenced Onion Architecture, Clean Architecture and others. Core concepts Port: An interface defined by the core representing required or offered capabilities (repositories, notification, input interfaces). Adapter: Concrete implementation that translates between external systems and ports (DB adapters, HTTP controllers, message producers/consumers). Primary (driving) vs Secondary (driven): Driving adapters initiate interaction (UI, cron, API callers); driven adapters are called by core (DB, external APIs, message brokers). Dependency direction: Adapters depend on core interfaces (ports); domain/core must not depend on infrastructure (aligns with DIP). Design principles Single Responsibility — separate domain rules from infrastructure concerns. Dependency Inversion — depend on abstractions (ports). Separation of Concerns and Interface Segregation — focused ports per use case. Inversion of Control — core declares needs; adapters are injected at composition root. Typical components & flows Domain model: entities, value objects, domain services (pure, no I/O). Application/use cases: orchestrate domain behavior and depend only on ports. Adapters: inbound/driving (controllers, CLI, schedulers) and outbound/driven (DB, HTTP clients, message brokers). Example flow: HTTP controller (driving) → use case → repository port → DB adapter → back to controller. Implementation guidance Design steps: identify use cases → create domain/use‑case classes → define ports → implement adapters → wire in composition root. Project layouts: keep domain & ports in core module; adapters in inbound/outbound folders or separate modules for microservices. Wire dependencies via DI at a single composition root (startup). Practical examples (languages) Commonly demonstrated in Java/Spring, Node/TypeScript, and C#/.NET: each shows ports as interfaces, use cases as classes depending on those interfaces, adapters implementing interfaces, and wiring at startup. The pattern and responsibilities remain the same across languages. Complementary patterns Domain‑Driven Design (DDD) — ports map naturally to repositories and services. CQRS & Event Sourcing — separate command/query use cases; events published via ports. Event‑driven and FP approaches — domain stays pure; adapters handle side effects. Testing strategies Unit tests: test domain/use cases in isolation by mocking ports. Adapter tests: integration tests for mapping and I/O (test containers, in‑memory DBs). Contract testing: consumer‑driven contracts for inter‑service boundaries. End‑to‑end: validate full flows including real adapters when needed. Deployment & observability Each microservice can implement hexagonal internals; adapters expose transport (HTTP/gRPC/events). Instrumentation, logging and metrics belong in adapters or via explicit ports (e.g., MetricsPort) to avoid domain contamination. Comparisons Layered (n‑tier): often more coupled; hexagonal emphasizes inward dependencies. Onion & Clean Architectures: conceptually similar—differences are mostly terminology and emphasis. Migration & refactoring Refactor incrementally: choose a bounded context/use case → extract domain/use case → create ports → implement adapters wrapping legacy systems. Use the Strangler Fig pattern to gradually replace legacy code. Common pitfalls Ports that leak infrastructure details (SQL/HTTP specific signatures). Domain code depending on frameworks or annotations. Over‑abstraction—creating unnecessary ports/adapters. Poor composition root: scattering wiring erodes IoC benefits. Case study types E‑commerce orders (OrderRepository, PaymentGateway, NotificationService). Event‑sourced ledgers (EventStore port, projections). IoT edge (SensorReader, TelemetryPublisher adapters). Future trends Fits cloud‑native/serverless where functions act as driving adapters. Stronger emphasis on contract‑driven and interface‑first design for polyglot ecosystems. Increasing use in event‑driven, asynchronous architectures and ML integration (treat models as ports). Practical checklist Start from use cases; model intent before wiring frameworks. Define intent‑driven ports (not technical APIs). Keep domain/use cases free of I/O and framework code. Wire adapters in a single composition root via DI. Test domain with unit tests & mock ports; test adapters with integration/contract tests. Avoid premature abstraction; add ports when they buy testability or replaceability. Further reading & conclusion Key sources: Alistair Cockburn (Hexagonal Architecture), Robert C. Martin (Clean Architecture), Eric Evans (DDD), and hands‑on project examples per language. Hexagonal Architecture is a pragmatic approach to keep business logic central, testable, and technology‑agnostic—beneficial for monoliths, microservices, and serverless systems alike. If you want, I can generate a starter project template (Java/TypeScript/C#), review your codebase for a migration plan, or produce concrete port/adapter/test skeletons for a chosen framework.

Let the lesson walk with you.

Podcast

Hexagonal Architecture podcast

0:00-4:03

Follow the trail that experts already trust.

Resources

Turn quick sparks into lasting recall.

Flashcards

Hexagonal Architecture flashcards

16 cards

Question

Click to flip
Answer

Prove the idea before it slips away.

Quizzes

Hexagonal Architecture quiz

12 questions

What is the primary purpose of Hexagonal Architecture (Ports and Adapters)?

Read deeper, connect wider, own the subject.

Deep Article

Hexagonal Architecture — A Deep Dive

Hexagonal Architecture (also known as the Ports and Adapters pattern) is a software architecture style intended to keep application core logic independent from external concerns (UI, databases, external services, messaging systems). It aims to make applications easier to test, evolve, and integrate by decoupling the business rules from their delivery mechanisms and infrastructure.

This article covers history, key concepts, theoretical foundations, practical implementations, comparisons with related architectures, code examples, testing strategies, migration guidance, common pitfalls, and future implications.


Table of contents

  • Introduction and motivations
  • History and origin
  • Core concepts
  • Ports and Adapters
  • Primary (driving) vs Secondary (driven) actors
  • Dependency rule and direction of flow
  • Hexagon metaphor
  • Theoretical foundations and principles
  • Single Responsibility Principle
  • Dependency Inversion Principle
  • Separation of concerns
  • Testability and inversion of control
  • Typical architecture and flows
  • Implementing Hexagonal Architecture
  • Design steps and boundaries
  • Folder/project structure examples
  • Example: Java + Spring (simple use case)
  • Example: Node/TypeScript (simple use case)
  • Example: C#/.NET (simple use case)
  • Complementary patterns and techniques
  • Domain-Driven Design (DDD)
  • CQRS and Event Sourcing
  • Messaging and event-driven systems
  • Testing strategies
  • Unit testing
  • Integration testing with adapters
  • Contract testing
  • End-to-end testing considerations
  • Deployment and operational considerations
  • Microservices and Hexagonal Architecture
  • Observability and monitoring
  • Comparisons with other architectures
  • Layered (n-tier)
  • Onion Architecture
  • Clean Architecture
  • Migration strategies and refactoring approaches
  • Common pitfalls and anti-patterns
  • Case studies and examples
  • Future trends and implications
  • Practical checklist and recommendations
  • Further reading

Introduction and motivations

When building software, you inevitably deal with two kinds of code:

  • Application (business) logic — the core domain rules and policies.
  • Infrastructure and delivery concerns — databases, web frameworks, messaging, UI, third-party APIs.

Hexagonal Architecture enforces a clear boundary between these concerns so the core domain remains independent from frameworks and external systems. This yields benefits:

  • Easier unit testing of domain logic without infrastructure dependencies.
  • Replaceable infrastructure (swap databases, UI, queue) with minimal impact.
  • Clear communication boundaries and better modularity.
  • Encourages explicit modeling of interaction points (ports) and their implementations (adapters).

History and origin

Hexagonal Architecture was proposed by Alistair Cockburn around 2005–2006. Cockburn's intent was to eliminate coupling between business logic and technology frameworks or delivery mechanisms and create a clear, testable architecture that supports different kinds of interactions with the application.

Cockburn used the hexagon as a metaphor: the application's core sits in the center, and each face of the hexagon is a port (an interface for communication). Adapters plug into ports to provide implementations for the outside world. Over time the pattern became widely known as "Ports and Adapters" and influenced other architectural styles: Onion Architecture (Jeffrey Palermo), Clean Architecture (Robert C. Martin), and more.


Core concepts

Ports and Adapters

  • Port: An abstract interface representing a required service or an offered capability. Ports are defined by the application core. They describe how the application expects to interact with external actors or services. Examples:
  • Repositories (persisting and retrieving domain entities)
  • Notification service (send emails)
  • Input interfaces (HTTP controllers, CLI commands)
  • Adapter: Concrete implementation that translates between the external world and the port. Adapters implement ports and live in the infrastructure layer. Examples:
  • SQL repository adapter using JDBC/ORM
  • REST controller adapter translating HTTP into application commands
  • Message queue adapter that routes messages to domain services

The key is that adapters depend on ports (interfaces), not the other way around.

Primary (driving) vs Secondary (driven) actors

  • Primary (driving) actors initiate interaction with the application: users, cron jobs, external systems calling the app. They are implemented by "driving adapters" that call the application through ports.
  • Secondary (driven) actors are services/devices the application calls: DB, external APIs, message brokers, email providers. They are implemented by "driven adapters" that implement ports the application expects.

This separation clarifies who initiates interactions and which parts are replaceable.

Dependency direction

The dependency rule: domain/core code should not depend on frameworks or infrastructure. Dependencies point inwards: adapters depend on the core interfaces. This aligns with the Dependency Inversion Principle (DIP).

Hexagon metaphor

Cockburn used a hexagon to show the app core surrounded by ports. The number of sides is arbitrary — the hexagon is a visual aid. The core defines the protocol, adapters plug into the ports, and the outside world interacts via adapters.

ASCII diagram:

`` +----------+ / \ UI DB | Core | CLI ->\ /-> Messaging +----------+ ``


Theoretical foundations and principles

Hexagonal Architecture embodies several software design principles:

  • Single Responsibility Principle: Keep domain logic focused on business rules; infrastructure concerns are separated into adapters.
  • Dependency Inversion Principle: High-level modules (domain) should not depend on low-level modules (DB). Both should depend on abstractions (ports).
  • Separation of Concerns: Each layer handles a distinct problem domain.
  • Interface Segregation: Ports should be focused interfaces tailored to use-cases.
  • Inversion of Control: The control flow is inverted — the application defines what it needs; adapters are injected.

These principles improve modularity, testability, and maintainability.


Typical architecture and flows

High-level components:

  • Domain model / business logic: Entities, value objects, domain services (pure, no I/O).
  • Application services / use cases: Orchestrate domain operations (still free of infrastructure).
  • Ports: Interfaces exposed by the application to be implemented by adapters.
  • Adapters:
  • Driving adapters (controllers, jobs, CLI) invoke application use cases.
  • Driven adapters (persistence, remote APIs, email clients) implement ports used by use cases.

Flow examples:

  1. User sends an HTTP request (driving adapter: Controller) → Controller calls application use case via port → Use case executes domain logic, uses repository port → Repository port implemented by DB adapter → DB adapter does I/O → Data returned to use case → Use case returns DTO to controller → Controller maps to HTTP response.
  1. Application needs to publish events → Application calls a MessagePort interface → Messaging adapter implements MessagePort sending messages to Kafka/RabbitMQ.

Implementing Hexagonal Architecture

Design steps and boundaries

  1. Identify core use cases: what the application must do.
  2. Define application services / use case classes that implement these use cases and express dependencies via interfaces (ports).
  3. Design domain model and domain services (pure behavior).
  4. Define ports for external interactions (persistence, external APIs, notifications).
  5. Implement adapters for each port (DB repositories, REST clients).
  6. Implement driving adapters: controllers, CLI, scheduled tasks.
  7. Wire dependencies via dependency injection at the composition root (application startup).

Key rule: Code inside the core (domain and use cases) must depend only on ports (interfaces) and domain objects.

Folder/project structure examples

Monolith single repo:

  • src/
  • domain/
  • model/
  • services/
  • application/
  • usecases/
  • ports/
  • adapters/
  • inbound/ (http, cli)
  • outbound/ (db, http clients, email)
  • config/ (composition root, DI)
  • tests/

Microservice with multiple projects (example in JVM):

  • service-core (domain + application ports + DTOs)
  • service-adapters (inbound + outbound adapters)
  • service-runner (composition root + deployment artifacts)

Example: Java + Spring Boot (simplified)

Domain and port:

```java // domain/Order.java public class Order { private UUID id; private List items; // domain behavior... }

// ports/OrderRepository.java public interface OrderRepository { Optional findById(UUID id); void save(Order order); } ```

Use case (application service):

```java // application/PlaceOrderService.java @Component public class PlaceOrderService { private final OrderRepository orderRepository; private final PaymentGateway paymentGateway; // port for external payment service

public PlaceOrderService(OrderRepository orderRepository, PaymentGateway paymentGateway) { this.orderRepository = orderRepository; this.paymentGateway = paymentGateway; }

public OrderResult placeOrder(PlaceOrderCommand command) { // business logic, validation, domain entity creation // call paymentGateway.charge(...) // save via orderRepository.save(order) } } ```

Adapter (driven) — DB repository:

```java // adapters/outbound/JpaOrderRepository.java @Repository public class JpaOrderRepository implements OrderRepository { private final JpaOrderEntityRepository jpaRepo; // Spring Data JPA

public Optional findById(UUID id) { return jpaRepo.findById(id).map(this::toDomain); }

public void save(Order order) { jpaRepo.save(toEntity(order)); } } ```

Adapter (driving) — REST controller:

```java // adapters/inbound/OrderController.java @RestController @RequestMapping("/orders") public class OrderController { private final PlaceOrderService placeOrderService;

@PostMapping public ResponseEntity placeOrder(@RequestBody PlaceOrderRequest req) { var result = placeOrderService.placeOrder(req.toCommand()); return ResponseEntity.ok(OrderResponse.from(result)); } } ```

Composition root: Spring Boot config wires concrete adapters into constructors of the service (DI).

Example: Node / TypeScript

Interfaces (ports) and domain:

```ts // src/ports/orderRepository.ts import { Order } from "../domain/order";

export interface OrderRepository { findById(id: string): Promise ; save(order: Order): Promise ...

Ready to see the full tree?

Clone the preview to open the complete learning structure, practice tools, and generated study materials.