Status: Accepted Datum: 2025-12-18 Entscheider: Entwicklungsteam Bezieht sich auf: ADR-015 Turso Database, ADR-018 Domain-Driven Design
Kontext
Das Aquarius-Backend muss folgende Anforderungen erfüllen:
Funktionale Anforderungen:
- REST API für zwei Frontend-Anwendungen
- Business-Logic für Wettkampf-Bewertung (komplex!)
- Datenvalidierung und -persistierung
- Berechnung von Endpunkten (Durchschnitt, Streichung, Schwierigkeitsfaktor)
Nicht-funktionale Anforderungen:
- Performance: Schnelle Response-Zeiten während Live-Bewertung
- Wartbarkeit: Ehrenamtliche Entwickler mit unterschiedlichen Skills
- Lesbarkeit: Code muss von Non-Experts verstanden werden
- Typsicherheit: Reduzierung von Runtime-Errors
- API-Dokumentation: Automatisch generiert
Entscheidung
Wir verwenden Python 3.11+ mit FastAPI als Backend-Framework.
Technologie-Stack
Backend Stack:
├── Python 3.11+ (Programmiersprache)
├── FastAPI 0.109+ (Web Framework)
├── Pydantic v2 (Datenvalidierung)
├── SQLAlchemy 2.0 (ORM)
├── Alembic (Datenbank-Migrationen)
├── libsql-experimental (Turso Client)
├── Uvicorn (ASGI Server)
└── Pytest (Testing Framework)
Projektstruktur
backend/
├── app/
│ ├── main.py # FastAPI App Entry
│ ├── database.py # DB Connection
│ ├── models/ # SQLAlchemy Models
│ │ ├── kind.py
│ │ ├── wettkampf.py
│ │ └── ...
│ ├── schemas/ # Pydantic DTOs
│ │ ├── kind.py
│ │ └── ...
│ ├── routers/ # API Endpoints
│ │ ├── stammdaten.py
│ │ ├── anmeldung.py
│ │ └── ...
│ ├── services/ # Business Logic
│ │ ├── anmeldung_service.py
│ │ ├── bewertung_service.py
│ │ └── ...
│ └── repositories/ # Data Access
│ ├── kind_repository.py
│ └── ...
├── tests/
├── alembic/ # DB Migrations
├── requirements.txt
└── pyproject.toml
Begründung
Pro Python + FastAPI
Python:
- ✅ Lesbarkeit: “Executable Pseudocode” - ideal für Ehrenamtliche
- ✅ Reiches Ökosystem: Zahlreiche Libraries für Data Processing
- ✅ Community: Große Community, viele Ressourcen
- ✅ Data Science Ready: Falls später Statistiken/ML gewünscht
- ✅ Type Hints: Python 3.11+ hat gute Type Support (mypy)
FastAPI:
- ✅ Performance: Einer der schnellsten Python-Frameworks (Starlette + Pydantic)
- ✅ Async Support: Native async/await für I/O-intensive Operationen
- ✅ Automatische Docs: OpenAPI (Swagger) out-of-the-box
- ✅ Typsicherheit: Pydantic-basierte Validation
- ✅ Modern: Python 3.6+ Type Hints als Basis
- ✅ DX: Exzellente Developer Experience, wenig Boilerplate
Pydantic v2:
- ✅ Validation: Automatische Request/Response-Validierung
- ✅ Serialization: JSON <-> Python Objects
- ✅ Type Safety: Runtime Type Checking
- ✅ OpenAPI: Automatische Schema-Generierung
- ✅ Performance: Pydantic v2 ist in Rust geschrieben (10x schneller)
Alternative: Node.js + Express/NestJS
Pro:
- ✅ Eine Sprache (TypeScript) für Frontend + Backend
- ✅ Große Community
Contra:
- ❌ Callback Hell / Async-Komplexität
- ❌ Weniger lesbar für Non-Experts
- ❌ Validation erfordert zusätzliche Libraries (zod, class-validator)
- ❌ ORM-Landscape fragmentiert (Prisma, TypeORM, Sequelize)
Entscheidung gegen Node: Python ist lesbarer, FastAPI hat bessere DX
Alternative: Java + Spring Boot
Pro:
- ✅ Enterprise-Ready, sehr ausgereift
- ✅ Starke Type Safety
Contra:
- ❌ Zu viel Boilerplate für kleine Liga (20 Kinder)
- ❌ Steile Lernkurve (Dependency Injection, Annotations)
- ❌ Langsamer Startup (JVM Warmup)
- ❌ Komplexer für Ehrenamtliche
Entscheidung gegen Spring: Overkill für Projektgröße
Alternative: Go + Gin/Echo
Pro:
- ✅ Sehr performant, kompilierte Sprache
- ✅ Einfache Deployment (Single Binary)
Contra:
- ❌ Weniger lesbar (Error Handling: if err != nil)
- ❌ Keine ORM-Tradition (SQL-Builder statt ORM)
- ❌ Kleinere Community für Web-Entwicklung
- ❌ Weniger Libraries für Business-Logic
Entscheidung gegen Go: Python ist wartbarer für Team
Konsequenzen
Positiv
- Auto-Generated API Docs:
/docsEndpoint mit Swagger UI - Type Safety: Pydantic validiert Requests automatisch
- OpenAPI Client: TypeScript-Client für Frontend auto-generierbar
- Testing: Pytest ist sehr ausdrucksstark, einfach zu schreiben
- Async Support: Nicht-blockierende DB-Operationen
Negativ
- Performance: Langsamer als kompilierte Sprachen (Go, Rust)
- GIL: Global Interpreter Lock limitiert Threading (aber async ist OK)
- Deployment: Python-Umgebung erforderlich (vs. Single Binary)
- Type Checking: Optional, erfordert mypy für Compile-Time Checks
Risiken
| Risiko | Wahrscheinlichkeit | Impact | Mitigation |
|---|---|---|---|
| Performance-Bottleneck bei 1000+ Wettkämpfen | Niedrig (20 Kinder Liga) | Mittel | Database Indexes, Query-Optimierung, Caching |
| Python-Versions-Inkompatibilität | Niedrig | Niedrig | Poetry/uv für Dependency Locking |
| Async-Code schwer zu debuggen | Mittel | Niedrig | Gute Tests, strukturiertes Exception Handling |
Implementierung
1. FastAPI Application
# app/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.routers import stammdaten, anmeldung, bewertung
app = FastAPI(
title="Aquarius API",
description="Swimming Competition Rating System",
version="1.0.0",
docs_url="/docs",
redoc_url="/redoc"
)
# CORS für Frontend
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173", "http://localhost:5174"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Router einbinden
app.include_router(stammdaten.router, prefix="/api/stammdaten", tags=["stammdaten"])
app.include_router(anmeldung.router, prefix="/api/anmeldungen", tags=["anmeldung"])
app.include_router(bewertung.router, prefix="/api/bewertungen", tags=["bewertung"])
@app.get("/health")
async def health_check():
return {"status": "healthy"}
2. Pydantic Schemas
# app/schemas/anmeldung.py
from pydantic import BaseModel, Field
from datetime import datetime
from typing import Optional
class AnmeldungCreate(BaseModel):
"""DTO für neue Anmeldung"""
kind_id: int = Field(..., description="ID des Kindes")
wettkampf_id: int = Field(..., description="ID des Wettkampfs")
figuren: list[int] = Field(..., description="IDs der gewünschten Figuren")
class AnmeldungResponse(BaseModel):
"""DTO für Anmeldung in Response"""
id: int
kind_id: int
wettkampf_id: int
startnummer: Optional[int] = None
status: str
erstellt_am: datetime
model_config = {"from_attributes": True} # Für ORM Models
3. Repository Pattern
# app/repositories/anmeldung_repository.py
from sqlalchemy.orm import Session
from app.models.anmeldung import Anmeldung
from typing import Optional
class AnmeldungRepository:
def __init__(self, session: Session):
self.session = session
def find_by_id(self, id: int) -> Optional[Anmeldung]:
return self.session.get(Anmeldung, id)
def find_by_kind_und_wettkampf(
self, kind_id: int, wettkampf_id: int
) -> Optional[Anmeldung]:
return self.session.query(Anmeldung).filter(
Anmeldung.kind_id == kind_id,
Anmeldung.wettkampf_id == wettkampf_id
).first()
def save(self, anmeldung: Anmeldung) -> Anmeldung:
self.session.add(anmeldung)
self.session.commit()
self.session.refresh(anmeldung)
return anmeldung
4. Service Layer
# app/services/anmeldung_service.py
from app.repositories.anmeldung_repository import AnmeldungRepository
from app.schemas.anmeldung import AnmeldungCreate
from app.models.anmeldung import Anmeldung, AnmeldungStatus
class AnmeldungService:
def __init__(
self,
anmeldung_repo: AnmeldungRepository,
kind_service: "KindService",
wettkampf_service: "WettkampfService"
):
self.anmeldung_repo = anmeldung_repo
self.kind_service = kind_service
self.wettkampf_service = wettkampf_service
def create_anmeldung(self, data: AnmeldungCreate) -> Anmeldung:
# Business Logic
kind = self.kind_service.get_kind(data.kind_id)
if not kind.ist_startberechtigt:
raise ValueError("Kind ist nicht startberechtigt")
# Doppelmeldung prüfen
existing = self.anmeldung_repo.find_by_kind_und_wettkampf(
data.kind_id, data.wettkampf_id
)
if existing:
raise ValueError("Kind bereits angemeldet")
# Anmeldung erstellen
anmeldung = Anmeldung(
kind_id=data.kind_id,
wettkampf_id=data.wettkampf_id,
status=AnmeldungStatus.VORLAEUFIG
)
return self.anmeldung_repo.save(anmeldung)
Validierung
Success Criteria
- ✅
/docsEndpoint liefert vollständige OpenAPI-Spezifikation - ✅ Alle Request/Response Bodies mit Pydantic validiert
- ✅ 100% Type Hints (mypy –strict ohne Errors)
- ✅ Test Coverage > 80%
- ✅ API Response Time < 100ms (p95)
Metriken
# Type Checking
mypy app/ --strict
# Test Coverage
pytest --cov=app --cov-report=html
# Performance Testing
locust -f tests/load/locustfile.py --host=http://localhost:8000
Referenzen
Historie
| Datum | Änderung | Autor |
|---|---|---|
| 2025-12-18 | Initiale Version | Team |