Design Patterns Tutorial 0/70 lessons ~6 min read Lesson 44

    Hexagonal Architecture

    Hexagonal Architecture (Ports and Adapters) puts domain at the center.

    Course progress0%
    Focus
    21 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Hexagonal Architecture (Ports and Adapters) puts domain at the center. Ports are interfaces the domain defines; Adapters are infrastructure implementations. HTTP, CLI, Postgres, Kafka, Stripe — all adapters. Domain never imports frameworks.

    The story

    A logistics rewrite replaced Spring-coupled code with Hexagonal layout. Switching hot routes from PostgreSQL to DynamoDB took 2 weeks instead of projected 6 months — domain unchanged; only persistence adapter swapped.

    The business problem

    Framework-coupled domain blocks testing, migration, and multi-channel delivery:

    • Vendor lock-in: domain imports Spring, Stripe, Mongoose directly.
    • Untestable core: can't run business logic without infrastructure.
    • Channel duplication: adding gRPC/CLI requires rewriting HTTP-tied code.
    • Migration cost: DB swap touches every service file.

    The problem teams faced

    Hexagonal fits when:

    • Domain must outlive frameworks and databases.
    • Multiple delivery mechanisms (HTTP, queue, CLI) share same use cases.
    • Tests need domain logic without Spring/container boot.
    • Infrastructure churn expected (DB migration, vendor swap).

    Understanding the topic

    Intent: Isolate domain via ports (interfaces) and adapters (implementations).

    • Domain core — entities, use cases, port interfaces.
    • Primary/driving adapters — HTTP controller, CLI, message consumer.
    • Secondary/driven adapters — repository, payment gateway, email sender.
    • Composition root — wires adapters to ports at startup.

    Internal architecture

    Hexagonal module layout:

    text
    domain/
    entities/Order.ts
    ports/OrderRepository.ts ← interface owned by domain
    usecases/PlaceOrder.ts
    adapters/
    in/HttpOrderController.ts ← driving
    out/PostgresOrderRepository.ts ← driven
    main.ts ← composition root wires in→usecase→out

    Visual explanation

    Three diagrams cover hex shape, port direction, and adapter swap:

    Ports and adapters
    HTTP Adapter
    Driving · in
    Use Case
    Domain core
    OrderRepository port
    Driven · out
    Postgres Adapter
    Swappable
    Domain defines ports; infrastructure implements adapters.
    Driving vs driven
    Driving
    Calls domain · HTTP · CLI
    Domain
    Use cases + ports
    Driven
    Domain calls · DB · API
    Both are adapters
    Outside hex
    Hexagon has no 'top' — all external world is adapter.
    DB migration via adapter
    Domain unchanged
    OrderRepository port
    PostgresAdapter
    Today
    DynamoAdapter
    Tomorrow
    Swap at root
    One binding
    2-week migration story — swap adapter, not domain.

    Informative example

    Before / after — Mongoose in service to port/adapter:

    ts
    // ❌ Domain coupled to Mongoose
    class OrderService {
    async place(cart: Cart) {
    return OrderModel.create({ items: cart.items }) // Mongoose in domain
    }
    }
    // ✅ Port in domain, adapter in infrastructure
    // domain/ports/OrderRepository.ts
    interface OrderRepository { save(order: Order): Promise<void> }
    // domain/usecases/PlaceOrder.ts
    class PlaceOrder {
    constructor(private orders: OrderRepository) {}
    async execute(cart: Cart) { await this.orders.save(Order.fromCart(cart)) }
    }
    // adapters/out/MongoOrderRepository.ts
    class MongoOrderRepository implements OrderRepository { /* Mongoose here */ }

    Execution workflow

    1Hexagonal adoption workflow
    1 / 4

    Define domain ports

    Interfaces in domain package — OrderRepository, PaymentGateway.

    Domain owns interface, not infra.

    Real-world use

    Alistair Cockburn's original pattern; adopted in DDD codebases, NestJS with domain modules, Go hexagonal layouts, many bank rewrites.

    Production case study

    Logistics Postgres → DynamoDB:

    • Scenario: Hot routes needed DynamoDB; domain Spring-coupled.
    • Decision: Hexagonal rewrite — ports for persistence.
    • Outcome: 2 weeks adapter swap vs 6 month projected rewrite.

    Trade-offs

    • Pro: domain testable; infra swappable; multi-channel reuse.
    • Con: more packages and wiring; learning curve for team.
    • Con: overkill when app is thin CRUD with stable stack.

    Decision framework

    • Use when infrastructure churn or multi-channel delivery expected.
    • Use when domain rules are valuable enough to protect from frameworks.
    • Avoid on throwaway MVPs with single HTTP + single DB forever.

    Best practices

    • Ports live in domain package — infrastructure implements, never defines.
    • Driving adapters map transport DTOs — don't leak HTTP types into domain.
    • Fake in-memory adapters for domain unit tests.

    Anti-patterns to avoid

    • Port interface defined in infrastructure layer — inverts dependency.
    • Anemic hexagon — ports/adapters but no domain rules in core.
    • Same class as port and adapter — missing seam.

    Common mistakes

    • Leaking DTO/ORM types through port interfaces.
    • Composition root scattered — multiple places wire adapters.

    Debugging tips

    • Bug in domain or adapter? — if domain tests pass, bug is in adapter mapping.

    Optimization strategies

    • Start with one bounded context hexagonal — don't boil the ocean.

    Common misconceptions

    • Hexagonal ≠ microservices. In-process pattern; can be monolith.
    • Same as Clean Architecture — convergent ideas; Clean adds concentric layer names.

    Advanced interview questions

    Interview Prep

    Practice concise answers, then expand each card for the explanation.

    3 questions
    1BeginnerQuestionPort vs Adapter?+

    Answer

    Port: interface domain defines (OrderRepository). Adapter: infrastructure implementation (PostgresOrderRepository) or entry point (HttpController).

    Follow-up

    Who owns port?
    2IntermediateQuestionDriving vs driven adapter?+

    Answer

    Driving initiates use case (HTTP in). Driven is called by domain (DB out). Both outside hexagon.

    Follow-up

    Kafka consumer?
    3AdvancedQuestionHexagonal vs Layered?+

    Answer

    Layered emphasizes horizontal stack. Hexagonal emphasizes domain center + explicit ports — better when infra churn and multi-channel.

    Follow-up

    2-week Dynamo story?

    Summary

    You can layout ports/adapters, keep domain framework-free, and swap persistence in weeks not months. Tell the DynamoDB 2-week story — if you can do that, you own Hexagonal.

    Ready to mark this lesson complete?Track your journey across the entire course.