Skip to content

Latest commit

 

History

History
174 lines (118 loc) · 14.2 KB

File metadata and controls

174 lines (118 loc) · 14.2 KB

🏛️ Architektur-Handbuch: arsnova.eu

Zuletzt aktualisiert: 2026-06-04 Rolle: Living Documentation (Documentation as Code)

Produktstatus (Stand 2026-06-04):

  • Produktionsreif umgesetzt: Epics 0–5, 7.1 (Team-Modus), der Kern von 8 (Q&A inkl. Sortiermodi und Tempo-Blitzlicht; offen: delegierte Moderation), 9 (Admin: Inspektion, Löschung, Behördenexport, Audit) und 10 (MOTD / Plattform-Kommunikation — ADR-0018, docs/features/motd.md).
  • Plattform: Epic 6 im Kern umgesetzt (Theme, i18n, Legal, Responsive); offen: 6.5 (Abschlussprüfung Barrierefreiheit / WCAG) und 6.6 (UX-Testreihen Thinking Aloud inkl. Umsetzung der Befunde — siehe Backlog.md, Story 6.6, und docs/EPIC6-AC-PRUEFUNG.md).
  • Plattformstatistik: Rekord max. Teilnehmende je Session (PlatformStatistic) plus 30-Tage-Verlauf der Session-Tagesrekorde (DailyStatistic, dailyHighscores) in health.stats und im Server-Status-Hilfedialog.
  • Quiz-Bewertung: SINGLE_CHOICE, MULTIPLE_CHOICE und SHORT_TEXT sind bewertbare Fragetypen; Leaderboards, Teamwertung, Bonus-Codes und Scorecards nutzen die gemeinsame Effective-Vote-Regel aus ADR-0028.
  • Offene Zielbilder: Delegierte Moderation bleibt ohne eigene /moderate-Route und ohne Moderator-Token noch Zielbild; Tempo ist als vordefiniertes Blitzlicht-Template im aktuellen quickFeedback-Code umgesetzt.

1. Einleitung & Philosophie

Dieses Handbuch beschreibt die Softwarearchitektur von arsnova.eu. Wir folgen dem Prinzip der "Living Documentation". Dieses Dokument und alle dazugehörigen Architekturentscheidungen (ADRs) liegen direkt im Git-Repository und entwickeln sich parallel zum Code weiter.

Das Hauptziel dieses Systems ist es, ein hochperformantes Audience-Response-System (Quiz-App für Hörsäle) zu schaffen, das konsequent datensparsam und DSGVO-orientiert aufgebaut ist. Die Quiz-Sammlung ist so gestaltet, dass Inhalte von Lehrenden standardmäßig nicht dauerhaft serverseitig gespeichert werden.


2. Der Technologie-Stack (High-Level)

Wir setzen auf einen modernen, stark typisierten TypeScript-Stack (Full-Stack), der auf Typsicherheit, Entwicklererfahrung (DX) und Echtzeit-Performance optimiert ist.

  • Frontend: Angular 21.2.x mit Signals (Zustandsverwaltung), Standalone Components und Angular Material 3 (tokenbasiert, im Angular-Frontend ohne Tailwind).
  • Backend: Node.js API mit tRPC v11 (für typsichere Aufrufe und WebSocket-Subscriptions).
  • Schemas: Zod v4 in @arsnova/shared-types und Backend-Routern; DTOs werden ueber .input() / .output() an tRPC-Prozeduren gebunden.
  • Datenbank (Persistenz): PostgreSQL angebunden über Prisma ORM 7.4.x.
  • Echtzeit-Broker (Flüchtig): Redis (Rate-Limiting, Blitzlicht-/Session-Code-Zustand, Tempo-Buckets, Sliding-Windows). Live-Updates in Sessions laufen über tRPC-WebSocket-Subscriptions; Status- und Teilnehmerpfade sind inzwischen bevorzugt signalgetrieben mit seltenem Resync, einzelne Live-Pfade nutzen weiterhin gezieltes Polling/Fallbacks. Es gibt keinen zentralen Redis-Pub/Sub-Pfad für jedes Frage-Event.
  • Offline & Sync Engine: Yjs (CRDTs für die Local-First Speicherung im Browser).

3. Kern-Architekturkonzepte

Um die Ziele des Projekts zu erreichen, müssen alle Entwickler folgende drei architektonische Säulen strikt einhalten:

3.1 Local-First & Zero-Knowledge (Die Yjs-Engine)

Die Quiz-Sammlung der Lehrenden (in der UI: Deine Quiz-Sammlung, Route /quiz) wird nicht dauerhaft auf dem Server gespeichert. Wenn eine Lehrperson ein Quiz erstellt, lebt dieses als CRDT-Dokument (Conflict-free Replicated Data Type) über Yjs primär in der lokalen IndexedDB ihres Browsers. Das Backend dient für die Quiz-Erstellung als WebSocket-Relay-Server, um Deltas (Änderungen) zwischen den Endgeräten der Lehrperson (z. B. PC und iPad) zu synchronisieren. Damit dieselbe Person ein Quiz auf einem anderen Gerät öffnen kann, erhält sie einen Sync-Link bzw. Sync-Code (Story 1.6a); nur wer diesen Schlüssel hat, kann das Quiz bearbeiten oder live steuern. Der Session-Beitrittscode für Teilnehmende gewährt keinen Zugriff auf die Quiz-Bearbeitung. Beim Start einer Live-Session wird eine Kopie des gewählten Quiz an den Server übermittelt (Quiz-Upload, Story 2.1a); diese Kopie wird nur für die Dauer der Session in PostgreSQL gehalten. Die dauerhafte "Single Source of Truth" der Quiz-Inhalte bleibt die lokale Yjs/IndexedDB der Lehrperson.

KI-gestützter Quiz-Import (externes LLM): System-Prompt kopieren und LLM-Antwort einfügen geschieht in derselben Quiz-Sammlung; der Prompt bezieht Preset und Optionen aus der Startseite bzw. dem localStorage der Preset-Optionen (nicht aus einem einzelnen Listen-Quiz). Vertrag und Pfade: ADR-0007.

3.2 End-to-End Typsicherheit (tRPC)

Wir verzichten auf klassische REST-Schnittstellen und das manuelle Schreiben von DTO-Klassen im Frontend. Durch die Nutzung von tRPC v11 im Monorepo (npm Workspaces) importiert das Angular-Frontend die Typen direkt aus der API-Schicht des Backends. Wenn sich das Datenbank-Schema (Prisma) ändert, schlägt der Frontend-Build sofort fehl.

Hinweis: Das Frontend führt @trpc/server als Dependency nur für die Bundler-Auflösung. @trpc/client v11 importiert intern Teile von @trpc/server (z. B. Observable/RPC). Ohne diese Abhängigkeit würde der Angular-Build die Imports nicht auflösen können; serverseitige Logik wird im Frontend nicht ausgeführt.

3.3 Security & Data-Stripping (Das DTO-Pattern)

Während einer Live-Sitzung müssen die Fragen an die Smartphones der Teilnehmenden gesendet werden. Das Backend lädt die Daten und muss zwingend ein DTO (Data Transfer Object) anwenden, bevor die Daten über WebSockets versendet werden. Lösungsrelevante Felder wie isCorrect werden serverseitig entfernt, um clientseitiges Cheating, etwa über Chrome DevTools, auszuschließen.


4. Architecture Decision Records (ADRs)

Wir dokumentieren jede signifikante Änderung an der Architektur, neue Bibliotheken oder Muster in Form von ADRs.

📂 Alle Entscheidungen finden sich im Ordner: ./decisions

Wichtige Basis-Entscheidungen:

Vertiefende Architektur-Dokumente:


5. Datenmodell (Single Source of Truth)

Unser relationales Datenmodell für flüchtige Live-Sessions, Quiz-Session-Kopien, Teilnehmende, Votes, Bonus-Token, Q&A, Session-Kanäle wie Blitzlicht, Session-Feedback sowie MOTD (Meldungen, Vorlagen, Locale-Texte, Interaktionszähler, Audit) und Plattformstatistiken (PlatformStatistic, DailyStatistic) wird zentral über Prisma verwaltet. Das aktuelle Schema findet sich in prisma/schema.prisma.

Hinweis zur Anonymität: Die App ist bewusst accountfrei. Es gibt kein User-/Account-Modell. Lehrende und Teilnehmende nutzen die App ohne Registrierung. Die Zuordnung Quiz ↔ Lehrperson erfolgt ausschließlich über Local-First (Yjs/IndexedDB) im Browser; der Server speichert keine Nutzerkonten.


6. Betrieb, CI/CD und Production-Deployment

Der produktive Rollout erfolgt über GitHub Actions (.github/workflows/ci.yml) mit klaren Gates:

  1. Build & Validate (inkl. typecheck-Job)
  2. Lint
  3. Tests
  4. Docker-Build
  5. Deploy-Freshness-Check: Nur der aktuelle main-HEAD darf weiter zum Production-Deploy.
  6. Deploy-Job, nur bei Push auf main und Repository-Variable DEPLOY_ENABLED=true; Voraussetzung: Alle Quality-Gates waren erfolgreich und github.sha ist weiterhin aktueller main-HEAD.

Der Deploy-Job ist an production als GitHub Environment gebunden und führt serverseitig scripts/deploy.sh mit DEPLOY_IMAGE (Digest) und DEPLOY_SHA aus. Das Skript pullt das gescannte GHCR-Image (kein Server-Build), migriert und prüft Health.

6.1 Deploy-Ablauf (serverseitig)

  • Ziel-Commit holen und exakt per DEPLOY_SHA detached auschecken
  • Digest-Image für app/pdf-worker pullen (compose pull — kein Server-Build)
  • Start von Postgres/Redis
  • Prisma-Migrationen (prisma migrate deploy)
  • App-Start/Update (prod-compose / compose up -d app)
  • Health-Wait, Digest-Nachweis, HTTP-Verifikation (/trpc/health.check, Frontend-Shell unter /de/)
  • Deploy-State (current.state/previous.state) und .env.arsnova-image schreiben

6.2 Betriebsdokumente

  • Admin-Betriebsfluss: docs/implementation/ADMIN-FLOW.md
  • Post-Deploy Go/No-Go Checkliste: docs/implementation/POST-DEPLOY-CHECKLIST.md

7. Performance-Leitplanken (Produktionsbetrieb)

Die wichtigsten umgesetzten Produktionsoptimierungen:

  • Redis-Hotpath entschärft: health.stats nutzt SCAN statt blockierendem KEYS.
  • DB-Indexierung nach Query-Mustern: gezielte Indizes für Session-, Vote-, Q&A-, Bonus- und Admin-Audit-Queries.
  • Polling-Reduktion / WebSocket-first: Subscription-Intervalle entschärft und dedupliziert; Frontend-Fallback-Polling reduziert.
  • Query-Payload-Reduktion: Hotpaths (getCurrentQuestionForHost/Student) laden nur noch benötigte Felder.

7.1 Grundsatz: Performance nicht ohne Sicherheitskontext optimieren

Für arsnova.eu gilt architektonisch:

  • Sicherheit, Autorisierung und Datenschutz sind keine optionalen Aufpreise auf Performance.
  • Maximale Performance und maximale Sicherheitskontrolle sind oft ein Zielkonflikt, kein gemeinsames Maximum.
  • Jede Optimierung muss deshalb auch ihre Folgen für Autorisierung, Missbrauchsschutz, Widerrufbarkeit, Auditierbarkeit und Datenminimierung benennen.

Typische Spannungsfelder:

  • Mehr Sicherheitsprüfungen bedeuten oft mehr Roundtrips, Hash-/Token-Prüfungen, Redis-Lookups oder zusätzliche Persistenz.
  • Weniger Prüfpfade machen Hotpaths schneller, vergrößern aber das Risiko von Rolleneskalation, Missbrauch oder stillen Fehlannahmen.
  • Mehr lokale Caches und Mirror verbessern Reaktionszeit und Offline-Verhalten, können aber Vertrauensannahmen, Recovery-Logik und Sicherheitssemantik komplizierter machen.
  • Mehr serverseitige Kontrolle verbessert Widerrufbarkeit und Nachweisbarkeit, schwächt aber Local-First und Zero-Knowledge.

Leitregel für neue Features:

  • erst das notwendige Sicherheitsniveau festlegen
  • dann innerhalb dieses Rahmens den Hotpath optimieren
  • Optimierungen bevorzugen, die ohne Sicherheitsabbau wirken

Grundprinzip für neue Features:

  • erst bestehende Subscription-Pfade nutzen
  • Polling nur als Fallback
  • bei Last-Hotspots selektive Prisma-select statt breiter include