Status: Accepted Datum: 2025-12-18 Entscheider: Entwicklungsteam Bezieht sich auf: ADR-014 FastAPI Backend, ADR-016 PWA Architecture
Kontext
Die Aquarius-Anwendung muss in zwei unterschiedlichen Betriebsmodi funktionieren:
1. Planungs-Modus (Online)
- Büro/Desktop-Umgebung
- Stabile Internetverbindung
- Zentrale Datenverwaltung
2. Durchführungs-Modus (Hybrid Online/Offline)
- Schwimmbad-Umgebung
- Kritisch: Potenziell instabile/keine Internetverbindung
- Tablet-basiert (iPads, Android Tablets)
- Live-Bewertung darf nicht durch Netzwerkprobleme unterbrochen werden
- Automatische Synchronisation wenn Verbindung wieder da
Anforderungen:
- ✅ Offline-Fähigkeit für Tablets am Schwimmbad
- ✅ Automatische Synchronisation
- ✅ Konfliktauflösung (Last-Write-Wins ist OK für kleine Liga)
- ✅ Niedrige Latenz für Live-Bewertung
- ✅ Einfache Entwicklung (lokales SQLite
für Tests)
- ✅ Günstiger Betrieb (kleine Liga, ~20 Kinder)
Entscheidung
Wir verwenden Turso (libSQL) als Datenbank mit Edge-Replication und Embedded Replicas.
Technologie-Stack
Database Stack:
├── Turso Cloud # Primäre Datenbank (Edge-hosted)
│ ├── libSQL Server # SQLite-kompatibler Server
│ ├── Edge Replication # Multi-Region für Latenz
│ └── HTTP API # Zugriff über HTTPS
├── Embedded Replicas # Lokale SQLite auf Tablets
│ ├── libsql-client (Python) # Backend-Zugriff
│ └── Auto-Sync # Bidirektionale Replikation
└── SQLite (Development) # Lokale Entwicklung
Architektur-Diagramm
┌─────────────────────────────┐
│ Turso Cloud │
│ (Primary Database) │
│ - Edge Nodes (Global) │
└──────────┬──────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Backend │ │ Tablet 1 │ │ Tablet 2 │
│ Server │ │ (Embedded) │ │ (Embedded) │
│ │ │ │ │ │
│ libSQL │ │ libSQL │ │ libSQL │
│ Client │ │ Replica │ │ Replica │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
└─────────────────┴─────────────────┘
Sync when online
Begründung
Pro Turso (libSQL)
SQLite-Kompatibilität:
- ✅ Entwicklung mit lokalem SQLite (keine Cloud nötig)
- ✅ Bekanntes SQL-Syntax
- ✅ File-basiert, einfaches Backup
- ✅ Exzellente SQLAlchemy-Integration
Edge-Replication:
- ✅ Niedrige Latenz (nächster Edge-Node < 50ms)
- ✅ Global verteilt (Frankfurt, Amsterdam, etc.)
- ✅ Automatisches Failover
Embedded Replicas:
- ✅ Offline-First: Tablet funktioniert ohne Internet
- ✅ Lokale Reads: Keine Netzwerk-Latenz
- ✅ Auto-Sync: Automatische Synchronisation wenn Online
- ✅ Conflict Resolution: Last-Write-Wins (ausreichend für kleine Liga)
Betrieb:
- ✅ Kostenlos bis 9 GB Storage (mehr als genug für 20 Kinder)
- ✅ Managed Service: Keine Server-Verwaltung
- ✅ Backups: Automatisch, Point-in-Time Recovery
- ✅ Schema-Migrationen: Via Alembic (wie normale SQLite)
Alternative: PostgreSQL + eigener Sync
Pro:
- ✅ Ausgereift, große Community
- ✅ Fortgeschrittene Features (Triggers, Stored Procedures)
Contra:
- ❌ Kein natives Offline-Support
- ❌ Eigener Sync-Mechanismus erforderlich (komplex!)
- ❌ Server-Betrieb erforderlich (Wartung, Updates)
- ❌ Höhere Kosten (Server + Traffic)
Entscheidung gegen PostgreSQL: Offline-Sync müssten wir selbst bauen
Alternative: PouchDB + CouchDB
Pro:
- ✅ Offline-First Design
- ✅ Bidirektionale Sync
Contra:
- ❌ NoSQL (kein SQL, andere Query-Sprache)
- ❌ Keine Joins (denormalisiert)
- ❌ Weniger ORM-Support
- ❌ Komplexere Datenmodellierung für relationale Daten
Entscheidung gegen CouchDB: Relationales Modell passt besser
Alternative: Supabase (PostgreSQL + Realtime)
Pro:
- ✅ PostgreSQL-basiert
- ✅ Realtime-Subscriptions
Contra:
- ❌ Kein natives Offline/Embedded
- ❌ Realtime nur über WebSocket (nicht für Tablets ideal)
- ❌ Teurer als Turso
Entscheidung gegen Supabase: Offline-Support nicht so gut wie Turso
Konsequenzen
Positiv
- Entwickler-Experience: Lokales SQLite für Entwicklung/Tests
- Einfache Migrationen: Alembic + SQLite/libSQL = bewährte Kombination
- Offline-Fähigkeit: Wettkampf funktioniert auch ohne Internet
- Keine Server-Wartung: Managed Service
- Günstiger Betrieb: Free Tier reicht aus
Negativ
- Beta-Status: Turso ist noch relativ neu (seit 2023)
- Vendor Lock-In: Embedded Replicas sind Turso-spezifisch
- Feature-Limit: Weniger Features als PostgreSQL (keine Stored Procedures)
- Community: Kleinere Community als PostgreSQL
Risiken
| Risiko | Wahrscheinlichkeit | Impact | Mitigation |
|---|---|---|---|
| Turso-Service-Ausfall | Niedrig | Hoch | Embedded Replicas funktionieren offline, lokales Backup |
| Sync-Konflikte bei gleichzeitigen Schreibvorgängen | Mittel | Niedrig | Last-Write-Wins ist OK, keine kritischen Race Conditions |
| Migration zu anderer DB nötig | Niedrig | Hoch | SQLite-kompatibel → Einfacher Export, Standard SQL |
| Beta-Features brechen | Mittel | Mittel | Versionierung, gute Tests |
Implementierung
1. Backend-Konfiguration
# app/database.py
import os
from sqlalchemy import create_engine, event
from sqlalchemy.orm import sessionmaker
from dotenv import load_dotenv
load_dotenv()
# Turso Connection String
TURSO_DATABASE_URL = os.getenv(
"TURSO_DATABASE_URL",
"file:./aquarius.db" # Fallback: lokales SQLite für Development
)
TURSO_AUTH_TOKEN = os.getenv("TURSO_AUTH_TOKEN", "")
# SQLAlchemy Engine
if TURSO_DATABASE_URL.startswith("libsql://"):
# Production: Turso Cloud
engine = create_engine(
TURSO_DATABASE_URL,
connect_args={"auth_token": TURSO_AUTH_TOKEN},
echo=False
)
else:
# Development: lokales SQLite
engine = create_engine(
TURSO_DATABASE_URL,
connect_args={"check_same_thread": False},
echo=True
)
# Foreign Keys aktivieren (wichtig für SQLite!)
@event.listens_for(engine, "connect")
def set_sqlite_pragma(dbapi_conn, connection_record):
cursor = dbapi_conn.cursor()
cursor.execute("PRAGMA foreign_keys=ON")
cursor.close()
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def get_db():
"""Dependency für FastAPI"""
db = SessionLocal()
try:
yield db
finally:
db.close()
2. Embedded Replica (Frontend PWA)
// frontend/apps/execution/src/lib/db.ts
import { createClient } from "@libsql/client";
const client = createClient({
url: "libsql://aquarius-db.turso.io",
authToken: import.meta.env.VITE_TURSO_AUTH_TOKEN,
// Embedded Replica für Offline
syncUrl: "libsql://aquarius-db.turso.io",
syncInterval: 60, // Sync alle 60 Sekunden
});
// Sync manuell triggern
export async function syncDatabase() {
await client.sync();
}
// Query-Funktion
export async function query<T>(sql: string, params: any[] = []): Promise<T[]> {
const result = await client.execute({ sql, args: params });
return result.rows as T[];
}
3. Alembic-Migrationen
# Initiale Migration erstellen
alembic revision --autogenerate -m "Initial schema"
# Migration auf Turso anwenden
TURSO_DATABASE_URL=libsql://aquarius-db.turso.io \
TURSO_AUTH_TOKEN=your_token \
alembic upgrade head
4. Connection String Beispiele
# .env.example
# Development (lokales SQLite)
TURSO_DATABASE_URL=file:./aquarius.db
TURSO_AUTH_TOKEN=
# Production (Turso Cloud)
TURSO_DATABASE_URL=libsql://aquarius-db.turso.io
TURSO_AUTH_TOKEN=eyJhbGc...your_token
# Test (In-Memory)
TURSO_DATABASE_URL=file::memory:?cache=shared
TURSO_AUTH_TOKEN=
Validierung
Success Criteria
- ✅ Backend kann mit lokalem SQLite UND Turso Cloud arbeiten
- ✅ Embedded Replica funktioniert offline (Tablet ohne Internet)
- ✅ Sync erfolgt automatisch bei Verbindung
- ✅ Alembic-Migrationen funktionieren
- ✅ Foreign Key Constraints werden enforced
Metriken
# Connection-Test
python -c "from app.database import engine; print(engine.execute('SELECT 1').fetchone())"
# Sync-Performance messen
turso db show aquarius-db --json | jq '.size'
# Latenz testen
curl -w "@curl-format.txt" -o /dev/null -s https://aquarius-db.turso.io
Offline-Test
- Tablet mit Embedded Replica
- Netzwerk trennen (Flugmodus)
- Bewertungen erfassen
- Netzwerk wieder aktivieren
- Verifizieren: Daten auf Server synchronisiert
Referenzen
Historie
| Datum | Änderung | Autor |
|---|---|---|
| 2025-12-18 | Initiale Version | Team |