Skip to content

Hexagonal Architecture Example

This example demonstrates how to use Pacta to enforce Hexagonal Architecture (also known as Ports and Adapters).

Architecture Overview

flowchart TB
    subgraph Driving["Driving Side (Primary)"]
        PA[Primary Adapters<br/>Controllers, CLI, Event Handlers]
    end

    subgraph Application["Application Core"]
        IP[Inbound Ports<br/>Use Case Interfaces]
        subgraph Domain["DOMAIN"]
            D[Entities<br/>Domain Services]
        end
        OP[Outbound Ports<br/>Repository Interfaces]
    end

    subgraph Driven["Driven Side (Secondary)"]
        SA[Secondary Adapters<br/>Database, APIs, Queues]
    end

    PA -->|uses| IP
    IP -->|calls| D
    D -->|uses| OP
    SA -.->|implements| OP

    style Domain fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
    style D fill:#bbdefb,stroke:#1565c0
    style IP fill:#fff8e1,stroke:#f57f17
    style OP fill:#fff8e1,stroke:#f57f17
    style PA fill:#f3e5f5,stroke:#7b1fa2
    style SA fill:#e8f5e9,stroke:#2e7d32

Directory Structure

src/
├── domain/                    # Core business logic (center of hexagon)
│   ├── product.py             # Domain entity
│   └── product_service.py     # Domain service
├── ports/
│   ├── inbound/               # Driving ports (use case interfaces)
│   │   └── catalog_use_case.py
│   └── outbound/              # Driven ports (repository interfaces)
│       └── product_repository.py
└── adapters/
    ├── primary/               # Driving adapters (controllers, CLI)
    │   └── api_controller.py
    └── secondary/             # Driven adapters (database, APIs)
        └── postgres_product_repository.py

Key Rules

Rule Description
Domain → Adapters Forbidden - Domain must not know about adapters
Domain → Outbound Ports Allowed - Domain uses repository interfaces
Ports → Adapters Forbidden - Ports are interfaces, adapters implement them
Primary Adapters → Domain Warning - Should go through inbound ports
Secondary Adapters → Outbound Ports Allowed - Implements the interface
Adapters → Adapters Forbidden - Adapters should be independent

Usage

# Run architecture check
pacta scan . --model architecture.yml --rules rules.pacta.yml

# Expected output (clean architecture):
# ✓ 0 violations

Dependency Flow

Dependencies always point inward toward the domain:

flowchart LR
    PA[Primary<br/>Adapters] --> IP[Inbound<br/>Ports] --> D((DOMAIN))
    SA[Secondary<br/>Adapters] --> OP[Outbound<br/>Ports] --> D

    style D fill:#e3f2fd,stroke:#1565c0,stroke-width:2px

This ensures: - Domain is isolated and testable - Adapters can be swapped without changing business logic - The application is framework-agnostic