What is Hexagonal Architecture?
Hexagonal Architecture, also known as Ports and Adapters pattern, is an architectural pattern that aims to create loosely coupled application components that can be easily tested and maintained.Core Principles
- Domain at the Center: Business logic is isolated from external concerns
- Dependency Inversion: Outer layers depend on inner layers, never the reverse
- Technology Agnostic: Core domain doesn’t know about frameworks or databases
- Testability: Each layer can be tested in isolation
The Hexagon Metaphor
Implementation in Backend
The backend follows a clear three-layer structure within each module.Example: Product Module
Directory structure:1. Domain Layer (Core)
The domain contains pure business logic and entities. Example: Product Entity (Product/Domain/product.py:1)
Product/Domain/movement.py:1)
2. Ports Layer (Interfaces)
Ports define contracts that adapters must implement. Example: Repository Port (Product/Ports/repository.py:1)
3. Adapters Layer (Implementations)
A. Repository Adapter (Data Access)
Example: SQLAlchemy Repository (Product/Adapters/product_repository.py:1)
B. Controller Adapter (HTTP Interface)
Example: Flask Controller (Product/Adapters/product_controller.py:13)
Implementation in Frontend
The frontend mirrors the backend architecture with React-specific adaptations.Example: Product Module
Directory structure:1. Domain Layer
Example: Product Model (Product/Domain/models/Product.js:1)
2. Ports Layer
Example: Repository Port (Product/Ports/ProductRepository.js:1)
3. Adapters Layer
Example: API Adapter (Product/Adapters/ApiProductRepository.js:1)
4. Application Layer
Example: Use Case Hook (Product/Application/useProductActions.js:1)
5. UI Layer
Example: React Component (Product/UI/pages/ProductListPage.jsx:1)
Benefits of Hexagonal Architecture
1. Testability
Each layer can be tested independently:2. Flexibility
Swap implementations easily:3. Maintainability
Clear separation of concerns:- Domain changes: Only affect Domain layer
- Database changes: Only affect Repository adapter
- API changes: Only affect Controller adapter
- UI changes: Only affect UI layer
4. Framework Independence
The core domain doesn’t depend on:- Flask (could migrate to FastAPI)
- SQLAlchemy (could migrate to SQLModel or raw SQL)
- React (could migrate to Vue or Svelte)
Data Flow Through Layers
Request Flow (Frontend → Backend)
Module Examples
The same pattern is applied consistently across all modules:Auth Module
- Domain:
Auth/Domain/auth_service.py,Auth/Domain/token.py - Ports:
Auth/Ports/token_provider.py,Auth/Ports/email_provider.py - Adapters:
Auth/Adapters/jwt_token_provider.py,Auth/Adapters/mock_email_provider.py
Stakeholder Module
- Domain:
Stakeholder/Domain/customer.py,Stakeholder/Domain/supplier.py - Ports:
Stakeholder/Ports/customer_repository.py - Adapters:
Stakeholder/Adapters/customer_repository.py,Stakeholder/Adapters/customer_controller.py
Audit Module
- Domain:
Audit/Domain/audit_log.py - Ports:
Audit/Ports/audit_repository.py - Adapters:
Audit/Adapters/audit_repository.py,Audit/Adapters/audit_controller.py
Common Layer (Shared Kernel)
TheCommonLayer provides shared utilities used across all modules:
- Domain:
AuditableEntitybase class for all entities - Middleware: Authentication, logging, exception handling
- Utils: Date formatting, currency conversion, SKU generation
- Schemas: Common validation schemas
Best Practices
Do’s
- Keep domain pure: No framework imports in domain models
- Define clear interfaces: All ports should have explicit contracts
- One adapter per technology: Separate HTTP, database, email adapters
- Inject dependencies: Pass repositories to controllers via constructor
- Use abstract base classes: Enforce interface compliance in Python
Don’ts
- Don’t let domain depend on adapters: Always depend on ports
- Don’t mix concerns: Controllers shouldn’t access database directly
- Don’t leak implementation details: Port interfaces shouldn’t expose SQLAlchemy types
- Don’t skip the application layer: Use cases coordinate complex operations
- Don’t hardcode adapters: Use dependency injection or factory patterns
Related Documentation
- Architecture Overview - Full system architecture
- Database Schema - Entity relationships
- API Reference - HTTP endpoints documentation