Update documentation for Navigation V3 and tenant concept: Mark Navigation V2 as deprecated, link replacement documentation, and expand tenant concept details with frontend and backend integration guidelines.
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled

This commit is contained in:
2026-04-02 23:16:56 +02:00
parent 6595ec674f
commit 85282ea7b4
5 changed files with 116 additions and 48 deletions
@@ -1,66 +1,114 @@
---
type: Architecture
type: Architecture Reference
status: ACTIVE
owner: 🏗️ Lead Architect & 🧹 Curator
owner: 🧹 Curator & 🏗️ Lead Architect
last_update: 2026-04-02
sources:
- docs/01_Architecture/adr/0021-tenant-resolution-strategy-de.md
- Domain Workshop 2026-03-24
- docs/02_Guides/Event-First-Workflow.md
- docs/06_Frontend/Navigation_V3_Screen-Baum_und_Back-Stack.md
---
# Tenant‑Konzept: „Eine Veranstaltung = eine Datenbank“ (in einfacher Sprache)
# Tenant‑Konzept — Eine Veranstaltung = eine Datenbank (ein Tenant)
Kurz gesagt: Jede Veranstaltung bekommt ihre eigene kleine Datenbank. So bleiben Daten sauber getrennt, und die Meldestelle kann offline sicher arbeiten, ohne andere Veranstaltungen zu berühren.
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. Warum machen wir das?
## 1) Idee in Alltagssprache
- Sicherheit und Ordnung: Was zu Veranstaltung A gehört, landet nicht aus Versehen bei Veranstaltung B.
- Offline‑Tauglichkeit: Eine Datenbank pro Veranstaltung ist klein, lokal und schnell zu sichern/verteilen.
- Einfache Abrechnung: Die Veranstaltungs‑Kassa und die TeilnehmerKonten sind klar auf Event‑Ebene definiert.
- 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. Wie wird bestimmt, welche Datenbank benutzt wird?
## 2) Was bedeutet das fürs Datenbank‑Schema?
Siehe Architektur‑Entscheidung (ADR‑0021):
- 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`.
- Datei: `docs/01_Architecture/adr/0021-tenant-resolution-strategy-de.md`
- Kerngedanke: Die App leitet aus dem aktuellen „Arbeitskontext“ (gewählte Veranstaltung) den Tenant ab. Alle Lese/Schreib‑Operationen gehen automatisch in die richtige Event‑Datenbank.
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 im Überblick
## 3) Auswirkungen auf die API (Backend)
### a) Datenbank‑Schema (Backend)
- Jeder Request muss wissen, „in welchem Veranstaltungs‑Ordner“ er arbeiten soll.
- Primär über den HTTP‑Header `X-Event-Id: <event_slug>` (kanonisch). Alternativ kann die Subdomain/Host dies ausdrücken, z. B. `<event>.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).
- Pro Veranstaltung ein eigenes Schema/Datei (z. B. `event-{eventId}.db`).
- Tabellen wiederholen sich je Veranstaltung (z. B. `turniere`, `bewerbe`, `abteilungen`, `startlisten`, `kassa_belege`).
- Keine Vermischung zwischen Veranstaltungen. Für Auswertungen über mehrere Veranstaltungen braucht es einen Aggregations‑Prozess.
Fehlerbilder (vereinheitlicht):
- Unbekannte Veranstaltung → `404 Unknown event`.
- Veranstaltung gesperrt/archiviert → `423 Locked` (oder 403 je Endpoint‑Policy).
### b) API‑Design
- Jeder Request enthält implizit oder explizit den Event‑Kontext (Header, Pfad oder Session‑Scope).
- Schreib‑Operationen validieren, dass die Ziel‑IDs (Turnier, Bewerb, Abteilung) zur aktuellen Veranstaltung gehören.
- Export/Import: Datenpakete sind pro Veranstaltung abgegrenzt (leichte Weitergabe an Verband oder andere Systeme).
### c) Frontend (Navigation & State)
- Beim Eintritt in eine Veranstaltung wird der Tenant gesetzt; alle folgenden Screens arbeiten innerhalb dieses Kontexts.
- Wechsel der Veranstaltung leert relevante Caches und setzt den Back‑Stack definiertermaßen zurück (→ Navigation V2).
- Multi‑Turnier‑Fälle innerhalb EINER Veranstaltung bleiben möglich (Turnierkassa je Turnier, Konsolidierung in Veranstaltungs‑Kassa).
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. Grenzen & Trade‑offs
## 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.
---
## 5. Beziehung zur Domäne
## 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`