Hexagonal Architecture (Ports & Adapters) in Python SaaS
Hexagonal Architecture (Ports & Adapters) in Python SaaS
This diagram visualizes the structural separation of concerns in the python-saas template.
1. The Core Philosophy
The Hexagonal Architecture (also known as Ports and Adapters) ensures the Business Logic (Core) remains pure and isolated from external side effects like databases, APIs, or user interfaces.
In python-saas, this allows us to swap a PostgreSQL database for a NoSQL one, or change a REST API for a GraphQL one, without touching a single line of business logic.
2. Layer Definitions
The Core (Inner Circle)
- Entities: Pure data structures (Pydantic models) representing the business domain (e.g.,
User,Project). - Use Cases: The orchestration logic. This is where the actual “features” live. Use cases only interact with Interfaces, never concrete implementations.
- Interfaces (Ports): Abstract Base Classes (ABCs) that define the “contract” for what the core needs from the outside world (e.g.,
UserRepository).
The Infrastructure (Driven Adapters)
- Persistence: Concrete implementations of the core interfaces (e.g.,
SQLAlchemyUserRepository). - External Adapters: Clients for email services, AI LLM providers, or cloud storage.
- Configuration: How the system boots up, linking concrete adapters to abstract ports.
The UI Layer (Driving Adapters)
- Web API: FastAPI routers that receive requests and trigger the relevant Use Case.
- NiceGUI Interface: Reactive Python UI components that interact with the Core.
3. The Dependency Rule
Dependencies MUST always point inwards.
Infrastructure -> Core(Directly or via Ports)UI -> Core- NEVER
Core -> InfrastructureorCore -> UI.
4. Visual Flow
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#3b82f6', 'edgeColor': '#ffffff', 'tertiaryColor': '#1f2937'}}}%%
graph TD
subgraph UI_Layer [UI / Driving Adapters]
Main[ui/main.py]
API[api/routes.py]
end
subgraph Core_Layer [Core / Domain]
UC[use_cases/]
E[entities/]
I[interfaces/ / Ports]
end
subgraph Infra_Layer [Infrastructure / Driven Adapters]
DB[persistence/memory_repository.py]
Ext[adapters/external_api.py]
end
Main --> UC
API --> UC
UC --> E
UC --> I
DB -.-> I
Ext -.-> I
style UI_Layer fill:#064e3b,stroke:#10b981,stroke-width:2px,color:#fff
style Core_Layer fill:#1e3a8a,stroke:#3b82f6,stroke-width:4px,color:#fff
style Infra_Layer fill:#78350f,stroke:#f59e0b,stroke-width:2px,color:#fff
classDef default color:#fff,stroke:#fff
Key Principles
- Core Independence: The
core/folder contains no imports fromui/orinfrastructure/. - Ports (Interfaces): Use abstract base classes in
interfaces/to define how the core interacts with the outside world. - Adapters: Implementations in
infrastructure/(e.g., SQLAlchemy, Redis) satisfy the interfaces defined in the core. - Testability: The core can be tested in 100% isolation by mocking the ports.