agentskills.codes
HE

hexagonal-architecture

Design, implement, and refactor Ports & Adapters systems with clear domain boundaries, dependency inversion, and testable use-case orchestration across TypeScript, Java, Kotlin, and Go services. Use for: new features needing long-term maintainability, decoupling domain logic from frameworks/DB/HTTP,

Install

mkdir -p .claude/skills/hexagonal-architecture && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/14992" && unzip -o skill.zip -d .claude/skills/hexagonal-architecture && rm skill.zip

Installs to .claude/skills/hexagonal-architecture

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Design, implement, and refactor Ports & Adapters systems with clear domain boundaries, dependency inversion, and testable use-case orchestration across TypeScript, Java, Kotlin, and Go services. Use for: new features needing long-term maintainability, decoupling domain logic from frameworks/DB/HTTP, supporting multiple transports for the same use case, or replacing infrastructure without rewriting business rules.
416 chars✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)

About this skill

Hexagonal Architecture (Ports & Adapters)

Keeps business logic independent from frameworks, transport, and persistence. The core application depends on abstract ports; adapters implement those ports at the edges.

When to Use

  • Building new features where long-term testability and maintainability matter.
  • Refactoring layered or framework-heavy code where domain logic is entangled with I/O.
  • Supporting multiple transports for the same use case (HTTP, CLI, queue worker, cron job).
  • Replacing infrastructure (database, external API, message bus) without rewriting business rules.

Core Concepts

LayerResponsibilityConstraint
DomainBusiness rules, entities, value objectsNo framework or infrastructure imports
Application (Use Cases)Orchestrate domain behaviorDepends only on domain + port interfaces
Inbound PortsContracts for what the app can doInterfaces/types in application layer
Outbound PortsContracts for what the app needsInterfaces/types in application layer
AdaptersImplement ports with real infrastructureHTTP controllers, DB repos, SDK wrappers
Composition RootWires concrete adapters into use casesSingle, auditable wiring location

Dependency direction is always inward:

Adapters → Application/Domain
Application → Port interfaces
Domain → nothing external

Architecture Diagram

flowchart LR
  Client["Client (HTTP/CLI/Worker)"] --> InboundAdapter["Inbound Adapter"]
  InboundAdapter -->|"calls"| UseCase["UseCase (Application Layer)"]
  UseCase -->|"uses"| OutboundPort["OutboundPort (Interface)"]
  OutboundAdapter["Outbound Adapter"] -->|"implements"| OutboundPort
  OutboundAdapter --> ExternalSystem["DB / API / Queue"]
  UseCase --> DomainModel["Domain Model"]

Procedure

Step 1 — Model the use case boundary

Define a single use case with an explicit input DTO and output DTO. Strip all transport details (req, res, queue wrappers) before the boundary.

Step 2 — Define outbound ports first

Identify every side effect as a named port:

  • Persistence → OrderRepositoryPort
  • External services → PaymentGatewayPort
  • Cross-cutting → LoggerPort, ClockPort, UuidPort

Ports model capabilities, not technologies.

Step 3 — Implement the use case with pure orchestration

Use case class/function receives ports via constructor injection. It validates application-level invariants, coordinates domain rules, and returns plain data structures. No framework types enter here.

Step 4 — Build adapters at the edges

  • Inbound adapter: converts protocol input → use-case input; maps output/errors → protocol response.
  • Outbound adapter: maps app port contract → concrete API/ORM/query builder.
  • All mapping stays in adapters, never inside use cases.

Step 5 — Wire everything in a composition root

Instantiate adapters, inject them into use cases. Keep wiring in one auditable place; avoid hidden global singletons or service-locator patterns.

Step 6 — Test per boundary

  • Domain tests: pure business rules, no mocks.
  • Use-case unit tests: fake/stub every outbound port; assert outcomes and interactions.
  • Adapter contract tests: run against each adapter implementation.
  • Inbound adapter tests: verify protocol mapping in both directions.
  • Integration tests: real infrastructure (DB, API, queue).
  • E2E tests: critical user journeys end-to-end.

See testing guidance for detailed patterns.

Suggested Module Layout

Feature-first organization with explicit boundaries:

src/
  features/
    orders/
      domain/
        Order.ts
        OrderPolicy.ts
      application/
        ports/
          inbound/
            CreateOrder.ts        ← use-case input/output types
          outbound/
            OrderRepositoryPort.ts
            PaymentGatewayPort.ts
        use-cases/
          CreateOrderUseCase.ts
      adapters/
        inbound/
          http/
            createOrderRoute.ts
        outbound/
          postgres/
            PostgresOrderRepository.ts
          stripe/
            StripePaymentGateway.ts
      composition/
        ordersContainer.ts        ← composition root

Multi-Language Mapping

LanguagePorts locationUse-case styleWiring
TypeScriptapplication/ports/* as interfacesClass or function with constructor/arg injectionExplicit factory module
Javaapplication.port.in / application.port.outPlain class (@Service optional)Spring @Configuration or manual wiring class
Kotlinapplication.port interfacesClass with constructor injection (Koin/Dagger/Spring/manual)Module definitions or composition functions
GoSmall interfaces in the consuming application packageStructs with interface fields + New… constructorsWire in cmd/<app>/main.go

See language examples for concrete code samples.

Anti-Patterns to Avoid

  • Domain entities importing ORM models, web framework types, or SDK clients.
  • Use cases reading directly from req, res, or queue metadata.
  • Returning database rows directly from use cases without domain/application mapping.
  • Adapters calling each other directly instead of flowing through use-case ports.
  • Spreading dependency wiring across many files with hidden global singletons.

Best Practices Checklist

  • Domain and use-case layers import only internal types and port interfaces.
  • Every external dependency has a corresponding outbound port.
  • Validation occurs at boundaries (inbound adapter + use-case invariants).
  • Entities use immutable transformations (return new values instead of mutating state).
  • Infrastructure errors are translated to domain/application errors at adapter boundaries.
  • Composition root is explicit and easy to audit in one place.
  • Use cases are testable with simple in-memory fakes; no real infrastructure needed.
  • Refactoring starts from one vertical slice with behavior-preserving characterization tests.
  • Language/framework specifics stay in adapters — never in domain rules.

References

Search skills

Search the agent skills registry