Status: Akzeptiert Datum: 2025-12-17 Entscheider: Architektur-Team

Kontext

Das Backend benötigt einen Object-Relational Mapper (ORM) für Daten

zugriff auf die Turso/SQLite-Datenbank mit Type-Safety, Migrations-Support und Query-Building.

Entscheidung

Wir verwenden SQLAlchemy 2.0 mit dem neuen deklarativen Mapping-Stil als ORM.

Begründung

Vorteile

  • De-facto Standard für Python ORMs
  • SQLAlchemy 2.0 - Moderne API mit Type Hints
  • Async Support - Kompatibel mit FastAPI’s async/await
  • Typsicherheit - Vollständige MyPy/Pyright-Unterstützung
  • Flexible Queries - Von einfachen bis zu komplexen Abfragen
  • Migrations - Integration mit Alembic
  • Turso/libSQL-Kompatibilität - SQLite-Dialekt funktioniert
  • Relationship Loading - Lazy, Eager, Subquery Loading

Alternativen

Alternative Grund für Ablehnung
Django ORM Benötigt Django Framework
Tortoise ORM Kleinere Community, weniger Features
Peewee Zu simpel für komplexe Queries
Raw SQL Fehleranfällig, keine Type-Safety
PonyORM Generatorbasierte Syntax ungewöhnlich

Konsequenzen

Positiv

  • Typsichere Datenbankabfragen
  • Automatisches Schema-Management via Migrations
  • Lazy/Eager Loading optimiert Performance
  • N+1-Problem vermeidbar durch Relationship-Loading
  • Testbarkeit durch In-Memory SQLite

Negativ

  • Learning Curve für SQLAlchemy 2.0 (mittlere Komplexität)
  • Performance-Overhead bei sehr einfachen Queries (minimal)

Technische Details

from sqlalchemy import String, Integer, ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship

class Base(DeclarativeBase):
    pass

class Anmeldung(Base):
    __tablename__ = "anmeldung"

    id: Mapped[int] = mapped_column(primary_key=True)
    kind_id: Mapped[int] = mapped_column(ForeignKey("kind.id"))
    wettkampf_id: Mapped[int] = mapped_column(ForeignKey("wettkampf.id"))
    startnummer: Mapped[int | None] = mapped_column()

    # Relationships
    kind: Mapped["Kind"] = relationship(back_populates="anmeldungen")
    wettkampf: Mapped["Wettkampf"] = relationship()

# Async Session Usage
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine

engine = create_async_engine("sqlite+aiosqlite:///aquarius.db")

async def get_anmeldung(session: AsyncSession, id: int) -> Anmeldung | None:
    return await session.get(Anmeldung, id)

Dependencies:

{
  "sqlalchemy": "^2.0.0",
  "aiosqlite": "^0.19.0"  // Async SQLite driver
}

Architektur-Integration

  • Repository Pattern - SQLAlchemy Models in Repository-Schicht
  • 3-Layer Architecture - Repository → Service → Router
  • Async/Await - AsyncSession für non-blocking DB-Access