--- type: Architecture Reference status: ACTIVE owner: 🧹 Curator & 🏗️ Lead Architect last_update: 2026-04-02 sources: - docs/01_Architecture/adr/0021-tenant-resolution-strategy-de.md - docs/02_Guides/Event-First-Workflow.md - docs/06_Frontend/Navigation_V3_Screen-Baum_und_Back-Stack.md --- # Tenant‑Konzept — Eine Veranstaltung = eine Datenbank (ein Tenant) Dieses Dokument erklärt in einfacher Sprache, wie wir Daten pro Veranstaltung trennen. Grundlage ist ADR‑0021 (Schema‑per‑Tenant). Kurz gesagt: Für jede Veranstaltung gibt es eine eigene „Datenbank‑Schublade“. Nichts aus Veranstaltung A landet versehentlich in Veranstaltung B. Weitere Details in der technischen Entscheidung: - ADR‑0021: `docs/01_Architecture/adr/0021-tenant-resolution-strategy-de.md` --- ## 1) Idee in Alltagssprache - Stell dir jede Veranstaltung wie einen eigenen Ordner vor. In diesem Ordner liegen alle Tabellen und Daten nur für genau diese Veranstaltung. - Öffnest du eine andere Veranstaltung, arbeitest du automatisch in einem anderen Ordner. Es gibt keine Vermischung. - Dadurch sind Archivierung, Backup, Wiederherstellung oder Löschen pro Veranstaltung einfach und sicher. Technisch heißt das: „Schema‑per‑Tenant“. Ein „Schema“ ist wie ein separater Ordner in derselben Datenbank. Optional können sehr große/sensible Veranstaltungen sogar eine ganz eigene physische Datenbank bekommen. --- ## 2) Was bedeutet das fürs Datenbank‑Schema? - Pro Veranstaltung existiert ein eigenes Schema mit denselben Tabellen (z. B. `veranstaltungen`, `turniere`, `bewerbe`, `abteilungen`, …). - Es gibt KEINE `tenant_id`‑Spalte in jeder Tabelle. Die Trennung passiert über das eigene Schema. - Eine zentrale Registry im `control`‑Schema verwaltet alle Veranstaltungen/Tenants, z. B. Tabelle `control.tenants(event_id, schema_name, db_url, status, version, created_at)`. - Migrationen (Flyway) laufen je Schema. Jedes Schema hat eine eigene `flyway_schema_history`. Vorteile: - Geringeres Risiko von Datenleckagen. - Leichtere, DSGVO‑konforme Löschung: Ein Schema lässt sich vollständig entfernen/archivieren. - Unabhängige Migration und Versionierung je Veranstaltung möglich. --- ## 3) Auswirkungen auf die API (Backend) - Jeder Request muss wissen, „in welchem Veranstaltungs‑Ordner“ er arbeiten soll. - Primär über den HTTP‑Header `X-Event-Id: ` (kanonisch). Alternativ kann die Subdomain/Host dies ausdrücken, z. B. `.meldestelle.local`. - Das Backend prüft `X-Event-Id` gegen die Registry (`control.tenants`). Nur aktive Veranstaltungen sind beschreibbar; archivierte sind schreibgeschützt. - Autorisierung: Tokens enthalten erlaubte `events` (Scopes). Der Zugriff auf eine Veranstaltung wird gegen diese Liste geprüft. - Admin/Synchronisations‑Endpunkte dürfen ausnahmsweise einen expliziten `eventId`‑Pfadparameter verwenden (Fallback). Fehlerbilder (vereinheitlicht): - Unbekannte Veranstaltung → `404 Unknown event`. - Veranstaltung gesperrt/archiviert → `423 Locked` (oder 403 je Endpoint‑Policy). Praktische Hinweise für API‑Clients: - Immer `X-Event-Id` mitsenden, sobald es fachlich um eine konkrete Veranstaltung geht. - Admin‑Operation „Veranstaltung anlegen“ erzeugt Schema + Registry‑Eintrag. Erst danach sind Fach‑Endpunkte nutzbar. --- ## 4) Auswirkungen auf das Frontend (Navigation V3, Offline‑First) - V3 Routen führen den Kontext über IDs (z. B. `eventId`, `tournamentId`, …). Diese IDs bestimmen implizit den aktiven Tenant. - Beim Öffnen eines Deep‑Links inkl. `eventId` setzt der Client den `X-Event-Id`‑Header automatisch für Backend‑Aufrufe. - Wechsel der Veranstaltung im UI entspricht einem Tenant‑Wechsel. Daraus folgen klare Regeln: - Der aktuelle Navigations‑Stack (V3) wird auf die Root der neu gewählten Veranstaltung zurückgesetzt (kein Cross‑Event‑State). - Datenansichten, Caches und ViewModel‑States sind pro Veranstaltung getrennt zu halten. - Kassen‑Sichten: „Veranstaltungs‑Kassa“ aggregiert nur über Turniere derselben Veranstaltung (nie übergreifend). Offline‑First: - Lokal wird je Veranstaltung eine eigene Datenbasis geführt (analog zur Backend‑Trennung). Ein Sync arbeitet immer bezogen auf den aktuellen Event‑Kontext. UX‑Hinweise: - In Breadcrumbs/TopBar ist die aktive Veranstaltung sichtbar und schnell wechselbar. - Bei ungültigem Kontext (z. B. `eventId` existiert nicht mehr) zeigt die App einen Hinweis und bietet einen Rücksprung zur Veranstaltungs‑Auswahl an. --- ## 5) Entwickler‑Leitfaden (kurz) - Backend - In Gateways/Clients stets `X-Event-Id` setzen, sobald ein Event‑Kontext vorhanden ist. - Keine gemeinsamen Queries über mehrere Veranstaltungen im selben Request. - Flyway‑Migrationsskripte tenant‑sicher halten (keine absoluten Schema‑Namen in DDL, sofern sie dynamisch sein müssen). - Frontend - Routen enthalten die relevanten IDs; keine globalen, veranstaltungsübergreifenden Stores für Event‑Daten. - Beim Event‑Wechsel alle abhängigen ViewModels/Stores invalidieren bzw. neu initialisieren. - Deep‑Links enthalten `eventId`; beim Öffnen wird der Navigationspfad synthetisch aufgebaut (siehe V3‑Dokument). --- ## 6) Grenzen & Trade‑offs - Cross‑Event‑Suche/Auswertung erfordert separate Aggregation (bewusste Entscheidung zugunsten Offline‑First und Sicherheit). - Datenmigration (z. B. Zusammenlegen/Teilen von Veranstaltungen) braucht Tools/Assistenten. --- ## 7) Beziehung zur Domäne - Ubiquitous Language: `Veranstaltung` ist die Root. Kassen und TeilnehmerKonten sind auf Event‑Ebene verankert. - Zahlvorgänge können mehrere Rechnungen/Belege aus verschiedenen Turnieren derselben Veranstaltung ausgleichen. --- ## 8) Querverweise - Technische Details/Begründung: `docs/01_Architecture/adr/0021-tenant-resolution-strategy-de.md` - Navigation V3 Regeln: `docs/06_Frontend/Navigation_V3_Screen-Baum_und_Back-Stack.md` - Event‑First‑Workflow: `docs/02_Guides/Event-First-Workflow.md`