# Copilot Instructions: FastAPI Backend with Hexagonal Architecture
You are a Python/FastAPI expert. Create a backend following hexagonal architecture, SOLID principles, and KISS.
## ποΈ Project Structure
```
βββ .github/workflows/ # CI/CD (tests, linting, deploy)
βββ src/
β βββ main.py # FastAPI app
β βββ config.py # Pydantic Settings
β βββ dependencies.py # Dependency injection
β βββ domain/ # Business core (pure Python)
β β βββ entities.py # Business entities (dataclasses)
β β βββ ports.py # Interfaces (ABC)
β β βββ services.py # Business services (optional)
β βββ application/
β β βββ requests.py # Input DTOs (Pydantic)
β β βββ use_cases.py # Application logic
β β βββ api.py # FastAPI routes
β βββ infrastructure/ # One folder = one implementation
β βββ postgres/ # adapter.py + models.py
β βββ mongodb/ # adapter.py + models.py
β βββ email/ # adapter.py
βββ tests/
βββ unit/ # Unit tests
βββ doubles/ # Test doubles (fakes)
```
## π― Core Principles
### Hexagonal Architecture
- **Domain**: Completely independent, ZERO external imports (no FastAPI, SQLAlchemy, etc.)
- **Application**: Orchestrates use cases, depends only on domain
- **Infrastructure**: Implements ports (adapters), can depend on everything
### SOLID
- **SRP**: 1 class = 1 responsibility (1 use case = 1 business action)
- **OCP**: Extension via new adapters, never modify ports
- **LSP**: All implementations respect their port's contract
- **ISP**: Specific and targeted interfaces (no ports with 20 methods)
- **DIP**: Use cases depend on ports (abstractions), never on concrete adapters
### KISS (Keep It Simple, Stupid)
- Direct transformations: `Class(**other.__dict__)` or `Class(**model.model_dump())`
- **No methods** like `create()`, `from_entity()`, `to_entity()` β too complex
- **No Response DTOs** β return domain entities directly in API
- Readable code > "clever" code
- One file per concept (no unnecessary subdirectories)
## π Code Rules
### Python Best Practices
- **Python 3.11+** minimum with latest features (match/case, StrEnum, Self type)
- Type hints **mandatory** everywhere (use `from typing import ...`)
- Async/await for all I/O operations (database, HTTP calls, file operations)
- **Dataclasses** for domain entities (simple, immutable when possible with `frozen=True`)
- **ABC (Abstract Base Classes)** for ports/interfaces
- Google-style docstrings for all public methods/classes
- **Error handling**: Custom exceptions hierarchy (inherit from base domain exception)
- **Naming conventions**:
- Classes: `PascalCase`
- Functions/variables: `snake_case`
- Constants: `UPPER_SNAKE_CASE`
- Private attributes: `_leading_underscore`
- Use **context managers** (`async with`) for resource management
- Prefer **composition over inheritance**
- **No mutable default arguments** (use `None` and initialize in function body)
- Use **Enum** for constants/status values
- **List/Dict comprehensions** over loops when readable
- Dependency manager: **UV** (not Poetry/pip)
### FastAPI Best Practices
- **Dependency injection** via `Depends()` for all services, repositories, and configurations
- **Annotated types** for cleaner dependency injection: `Annotated[Service, Depends(get_service)]`
- **Pydantic V2** for all request/response validation with Field constraints
- **Explicit HTTP status codes**: Use `status.HTTP_201_CREATED` instead of `201`
- **Router organization**: Group related endpoints with `APIRouter(prefix="/api/v1/resource", tags=["resource"])`
- **Error handling**:
- Custom `HTTPException` with clear error messages
- Exception handlers with `@app.exception_handler()`
- Consistent error response format
- **Request/Response models**:
- Separate models for input (Request) and output (Response) when needed
- Use `response_model` parameter to control what gets returned
- Use `response_model_exclude_unset=True` to skip null values
- **Validation**:
- Use Pydantic `Field()` with validators: `min_length`, `max_length`, `ge`, `le`, `regex`
- Custom validators with `@field_validator`
- Model validators with `@model_validator`
- **Documentation**:
- Docstrings on every endpoint with Args, Returns, Raises sections
- Use `summary`, `description`, `response_description` in decorators
- Provide examples in Pydantic models with `model_config = ConfigDict(json_schema_extra={...})`
- **Security**:
- JWT authentication with `python-jose`
- Password hashing with `passlib[bcrypt]`
- OAuth2PasswordBearer for protected routes
- CORS configuration explicit and restrictive
- Rate limiting (use `slowapi` or custom middleware)
- Security headers middleware
- **Performance**:
- Use `BackgroundTasks` for non-blocking operations (emails, logs)
- Connection pooling for databases
- Caching with Redis/in-memory for frequent queries
- Pagination for list endpoints: `limit`/`offset` or cursor-based
- **Startup/Shutdown events**:
- Database connection initialization in `lifespan` context manager
- Graceful shutdown handling
- **Middleware**:
- Request ID tracking
- Logging middleware for all requests
- CORS middleware properly configured
- Compression middleware for large responses
- **Testing**:
- Use `TestClient` for API testing
- Override dependencies with `app.dependency_overrides`
- Test error cases and edge cases
### Tests
- **Test doubles** (fakes) for internal components (repositories, services)
- **Mocks** ONLY for external services (third-party APIs, email, S3, etc.)
- pytest + pytest-asyncio
- Minimum coverage: 80%
- Fixtures for test doubles injection
### Data Transformations
```python
# β
GOOD: Direct transformation
user_entity = User(**user_model.__dict__)
db_user = UserModel(**user_entity.__dict__)
request_dto = CreateUserRequest(**pydantic_model.model_dump())
# β BAD: Unnecessary intermediate methods
user_entity = User.from_model(user_model)
db_user = UserModel.from_entity(user_entity)
```
### Infrastructure
- One folder per implementation: `postgres/`, `mongodb/`, `rag/`, `email/`
- Each folder contains: `adapter.py` (port implementation) + `models.py` (if needed)
- Adapters implement ports defined in `domain/ports.py`
### API
- Routes in `application/api.py`
- Return domain entities directly (FastAPI serializes them automatically)
- No need for separate Response DTOs
## β
Checklist
### Architecture
- [ ] 3 distinct layers: domain / application / infrastructure
- [ ] Domain without any external dependencies
- [ ] Use cases depend on ports (interfaces), not adapters
- [ ] Infrastructure: one folder per adapter with `adapter.py` + `models.py`
### SOLID & KISS
- [ ] SRP: One class = one responsibility
- [ ] DIP: Depend on abstractions (ports)
- [ ] Direct transformations with `**.__dict__` or `**model.model_dump()`
- [ ] No methods like `create()`, `from_entity()`, `to_entity()`
### Code Quality
- [ ] Type hints everywhere
- [ ] Async/await for I/O
- [ ] Pydantic V2 for validation
- [ ] Tests with test doubles (no internal mocks)
- [ ] Coverage β₯ 80%
### DevOps
- [ ] GitHub Actions CI/CD (tests, linting, security)
- [ ] Docker + Docker Compose
- [ ] Documented `.env.example`
- [ ] README.md with quick start
## π« Absolutely Avoid
- β Importing FastAPI, SQLAlchemy, etc. in domain
- β Creating Response DTOs (return domain entities directly)
- β Complex transformation methods (`from_entity`, `to_entity`)
- β Mocking internal components in tests (use test doubles)
- β Over-engineering (unnecessary builders, factories)
- β Too-wide interfaces with too many methods
## π Critical Reminders
1. **Domain** = pure Python, zero external imports
2. **Use cases** depend on **ports** (abstractions), never on **adapters**
3. Transformations: `Class(**other.__dict__)` or `Class(**model.model_dump())`
4. Tests: test doubles (fakes) for internal, mocks for external only
5. SOLID + KISS above all: simplicity and design principles