A reference Python project for hexagonal architecture (ports and adapters) with Test-Driven Development.
python_hexagonal_tdd_example/
├── src/
│ └── example_app/
│ ├── domain/ # The hexagon (core business logic)
│ │ ├── models/ # Domain entities and value objects
│ │ │ └── example_entity.py
│ │ ├── ports/ # Interfaces (abstract base classes)
│ │ │ └── repository.py # Output port for persistence
│ │ └── use_cases/ # Application-specific business rules
│ │ └── example_use_case.py
│ │
│ └── adapters/ # Infrastructure layer (outside the hexagon)
│ ├── inbound/ # Driving adapters (e.g., REST API, CLI)
│ └── outbound/ # Driven adapters (e.g., database, APIs)
│ └── in_memory_repository.py
│
├── tests/ # Mirrors src structure, uses pytest markers
│ ├── conftest.py # Shared pytest fixtures
│ ├── domain/
│ │ ├── models/
│ │ ├── ports/
│ │ └── use_cases/
│ │ └── test_example_use_case.py # @pytest.mark.unit
│ └── adapters/
│ ├── inbound/
│ └── outbound/
│ └── test_in_memory_repository.py # @pytest.mark.integration
│
├── pyproject.toml # Project configuration
└── README.md
flowchart LR
subgraph Inbound["Inbound Adapters<br/>(adapters/inbound/)"]
REST["REST API"]
CLI["CLI"]
MQ["Message Queue"]
end
subgraph Domain["Domain - The Hexagon<br/>(domain/)"]
UC["Use Cases<br/>(use_cases/)"]
Models["Models<br/>(models/)"]
Ports["Ports<br/>(ports/)"]
UC --- Models
Models --- Ports
end
subgraph Outbound["Outbound Adapters<br/>(adapters/outbound/)"]
Repo["Repositories"]
ExtAPI["External APIs"]
DB["Database"]
end
Inbound --> Domain
Domain --> Outbound
- Domain Layer: The hexagon containing pure business logic, entities, ports, and use cases. Has no dependencies on external frameworks or infrastructure.
- Use Cases: Application-specific business rules that orchestrate domain objects and coordinate with adapters through ports.
- Ports: Interfaces that define how the domain communicates with the outside world.
- Adapters: Implementations of ports that connect to external systems.
Dependencies always point inward:
- Adapters depend on Ports
- Use Cases depend on Models and Ports
- Models have no external dependencies
uv sync# Run all tests
pytest
# Run only unit tests (using markers)
pytest -m unit
# Run only integration tests (using markers)
pytest -m integration
# Run tests by path
pytest tests/domain
pytest tests/adapters
# Run with coverage
pytest --cov=example_app --cov-report=htmlTests mirror the source structure and use pytest markers for categorization:
| Marker | Description | Example |
|---|---|---|
@pytest.mark.unit |
Fast, isolated, no external dependencies | Domain model tests |
@pytest.mark.integration |
May require external systems | Repository adapter tests |
Example:
import pytest
@pytest.mark.unit
class TestMyEntity:
def test_something(self):
...- Red: Write a failing test that defines expected behavior
- Green: Write the minimum code to make the test pass
- Refactor: Improve the code while keeping tests green
MIT