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.
Some checks failed
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:
Stefan Mogeritsch 2026-04-02 23:16:56 +02:00
parent 6595ec674f
commit 85282ea7b4
5 changed files with 116 additions and 48 deletions

View File

@ -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
---
# TenantKonzept: „Eine Veranstaltung = eine Datenbank“ (in einfacher Sprache)
# TenantKonzept — 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 ADR0021 (SchemaperTenant). Kurz gesagt: Für jede Veranstaltung gibt es eine eigene „DatenbankSchublade“. Nichts aus Veranstaltung A landet versehentlich in Veranstaltung B.
Weitere Details in der technischen Entscheidung:
- ADR0021: `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.
- OfflineTauglichkeit: Eine Datenbank pro Veranstaltung ist klein, lokal und schnell zu sichern/verteilen.
- Einfache Abrechnung: Die VeranstaltungsKassa und die TeilnehmerKonten sind klar auf EventEbene 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: „SchemaperTenant“. 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 DatenbankSchema?
Siehe ArchitekturEntscheidung (ADR0021):
- 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/SchreibOperationen gehen automatisch in die richtige EventDatenbank.
Vorteile:
- Geringeres Risiko von Datenleckagen.
- Leichtere, DSGVOkonforme 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) DatenbankSchema (Backend)
- Jeder Request muss wissen, „in welchem VeranstaltungsOrdner“ er arbeiten soll.
- Primär über den HTTPHeader `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/SynchronisationsEndpunkte 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 AggregationsProzess.
Fehlerbilder (vereinheitlicht):
- Unbekannte Veranstaltung → `404 Unknown event`.
- Veranstaltung gesperrt/archiviert → `423 Locked` (oder 403 je EndpointPolicy).
### b) APIDesign
- Jeder Request enthält implizit oder explizit den EventKontext (Header, Pfad oder SessionScope).
- SchreibOperationen validieren, dass die ZielIDs (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 BackStack definiertermaßen zurück (→ Navigation V2).
- MultiTurnierFälle innerhalb EINER Veranstaltung bleiben möglich (Turnierkassa je Turnier, Konsolidierung in VeranstaltungsKassa).
Praktische Hinweise für APIClients:
- Immer `X-Event-Id` mitsenden, sobald es fachlich um eine konkrete Veranstaltung geht.
- AdminOperation „Veranstaltung anlegen“ erzeugt Schema + RegistryEintrag. Erst danach sind FachEndpunkte nutzbar.
---
## 4. Grenzen & Tradeoffs
## 4) Auswirkungen auf das Frontend (Navigation V3, OfflineFirst)
- V3 Routen führen den Kontext über IDs (z. B. `eventId`, `tournamentId`, …). Diese IDs bestimmen implizit den aktiven Tenant.
- Beim Öffnen eines DeepLinks inkl. `eventId` setzt der Client den `X-Event-Id`Header automatisch für BackendAufrufe.
- Wechsel der Veranstaltung im UI entspricht einem TenantWechsel. Daraus folgen klare Regeln:
- Der aktuelle NavigationsStack (V3) wird auf die Root der neu gewählten Veranstaltung zurückgesetzt (kein CrossEventState).
- Datenansichten, Caches und ViewModelStates sind pro Veranstaltung getrennt zu halten.
- KassenSichten: „VeranstaltungsKassa“ aggregiert nur über Turniere derselben Veranstaltung (nie übergreifend).
OfflineFirst:
- Lokal wird je Veranstaltung eine eigene Datenbasis geführt (analog zur BackendTrennung). Ein Sync arbeitet immer bezogen auf den aktuellen EventKontext.
UXHinweise:
- 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 VeranstaltungsAuswahl an.
---
## 5) EntwicklerLeitfaden (kurz)
- Backend
- In Gateways/Clients stets `X-Event-Id` setzen, sobald ein EventKontext vorhanden ist.
- Keine gemeinsamen Queries über mehrere Veranstaltungen im selben Request.
- FlywayMigrationsskripte tenantsicher halten (keine absoluten SchemaNamen in DDL, sofern sie dynamisch sein müssen).
- Frontend
- Routen enthalten die relevanten IDs; keine globalen, veranstaltungsübergreifenden Stores für EventDaten.
- Beim EventWechsel alle abhängigen ViewModels/Stores invalidieren bzw. neu initialisieren.
- DeepLinks enthalten `eventId`; beim Öffnen wird der Navigationspfad synthetisch aufgebaut (siehe V3Dokument).
---
## 6) Grenzen & Tradeoffs
- CrossEventSuche/Auswertung erfordert separate Aggregation (bewusste Entscheidung zugunsten OfflineFirst 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 EventEbene 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`
- EventFirstWorkflow: `docs/02_Guides/Event-First-Workflow.md`

View File

@ -19,9 +19,9 @@
- [x] Ablauf: Veranstaltung anlegen → Turnier anlegen → Bewerbe anlegen → Abteilungen → Startliste
- [x] Dokument in `docs/01_Architecture/` oder `docs/02_Guides/` ablegen → `docs/02_Guides/Event-First-Workflow.md`
- [x] **A-3** | Navigation-V2 dokumentieren
- [x] **A-3** | Navigation-V3 dokumentieren
- [x] Aktuellen Screen-Baum und Back-Stack-Verhalten beschreiben
- [x] Dokument in `docs/06_Frontend/` ablegen → `docs/06_Frontend/Navigation_V2_Screen-Baum_und_Back-Stack.md`
- [x] Dokument in `docs/06_Frontend/` ablegen → `docs/06_Frontend/Navigation_V3_Screen-Baum_und_Back-Stack.md`
- [x] **A-4** | Tenant-Konzept dokumentieren (nach ADR-0021 vom Architect)
- [x] ADR-0021 in `docs/01_Architecture/ADRs/` verlinken → `docs/01_Architecture/adr/0021-tenant-resolution-strategy-de.md`

View File

@ -24,19 +24,34 @@
- frontend/features/veranstalter-feature/src/jvmMain/.../VeranstalterAuswahlScreen.kt (nutzt ViewModel/Intents)
- [x] **A-2** | Abteilungs-Logik im Bewerb-Dialog berücksichtigen
- [x] Beim Anlegen eines Bewerbs: Abteilungs-Auswahl als Teil des Dialogs
- [x] CSN-C-NEU: Automatischer Vorschlag der Pflicht-Teilung (ohne/mit Lizenz; R1/R2+)
- [x] Abteilungs-Typ setzen: `SEPARATE_SIEGEREHRUNG` oder `ORGANISATORISCH`
- [x] Dialog enthält Abteilungs-Auswahl als Teil des „Bewerb anlegen“-Flows (im selben Modal)
- [x] CSN-C-NEU: Automatischer Vorschlag der Pflicht-Teilung mit 4 Abteilungen:
- [x] Ohne Lizenz · R1
- [x] Ohne Lizenz · R2+
- [x] Mit Lizenz · R1
- [x] Mit Lizenz · R2+
- [x] Beim Auto-Vorschlag Default-Setzung des Abteilungs-Typs auf `SEPARATE_SIEGEREHRUNG`
- [x] Manuelle Umschaltung des Abteilungs-Typs möglich: `SEPARATE_SIEGEREHRUNG` oder `ORGANISATORISCH`
- [x] UX: Bei erkanntem Typ „CSN-C-NEU“ wird ein AssistChip „Pflicht-Teilung vorgeschlagen“ angezeigt
Referenzen:
- frontend/features/turnier-feature/src/commonMain/.../BewerbAnlegenViewModel.kt (State, Intents, Auto-Vorschlag)
- frontend/features/turnier-feature/src/jvmMain/.../TurnierBewerbeTab.kt (Button „Bewerb Einfügen“ öffnet Dialog)
Akzeptanzkriterien:
- [x] Der „Bewerb anlegen“-Dialog zeigt ein Eingabefeld „Bewerbs-Typ“ und eine Auswahl für den Abteilungs-Typ (zwei Chips)
- [x] Bei Eingabe „CSN-C-NEU“ wird automatisch die oben definierte 4er-Teilung in der Abteilungs-Liste angezeigt
- [x] Die Auto-Teilung kann angezeigt werden, ohne dass der Dialog neu geöffnet werden muss (Live-Reaktion auf Eingabe)
- [x] Der gesetzte Abteilungs-Typ ist im State sichtbar und wird vom Dialog korrekt reflektiert
- [x] Kein Vorschlag für andere Typen; Liste bleibt leer bis manuell hinzugefügt/implementiert (aktuell out-of-scope)
Referenzen (konkret):
- frontend/features/turnier-feature/src/commonMain/kotlin/at/mocode/turnier/feature/presentation/BewerbAnlegenViewModel.kt
- `BewerbAnlegenState`, `BewerbAnlegenIntent`, `applySuggestion()` (Auto-Vorschlag + Default-AbteilungsTyp)
- frontend/features/turnier-feature/src/jvmMain/kotlin/at/mocode/turnier/feature/presentation/TurnierBewerbeTab.kt
- `BewerbAnlegenDialog(...)`: Eingabe „Bewerbs-Typ“, AssistChip, Auswahl Abteilungs-Typ, Anzeige der vorgeschlagenen Abteilungen
---
## 🟠 Sprint B — Kurzfristig (nächste Woche)
- [ ] **B-1** | ViewModels für alle V2-Screens umsetzen
- [ ] **B-1** | ViewModels für alle V3-Screens umsetzen
- [ ] `TurnierViewModel`
- [ ] `BewerbViewModel` (inkl. Abteilungs-Logik)
- [ ] `PferdProfilViewModel`

View File

@ -1,15 +1,19 @@
---
type: Redirect
status: MOVED
type: Frontend
status: DEPRECATED
owner: 🧹 Curator
last_update: 2026-04-02
moved_to: docs/_archive/06_Frontend/Navigation_V2_Screen-Baum_und_Back-Stack.md
replaced_by: docs/06_Frontend/Navigation_V3_Screen-Baum_und_Back-Stack.md
archive_copy: docs/_archive/06_Frontend/Navigation_V2_Screen-Baum_und_Back-Stack.md
---
# Navigation V2 — verschoben ins Archiv
# Navigation V2 — DEPRECATED
Dieses Dokument wurde in das Archiv verschoben und durch „V3“ ersetzt.
Diese Datei ist veraltet. Wir haben uns auf Navigation V3 geeinigt. Bitte verwende ab sofort die aktuelle Fassung:
- Archiv: `docs/_archive/06_Frontend/Navigation_V2_Screen-Baum_und_Back-Stack.md`
- Aktuelle Version: `docs/06_Frontend/Navigation_V3_Screen-Baum_und_Back-Stack.md`
- Aktuelle Version (SSoT): docs/06_Frontend/Navigation_V3_Screen-Baum_und_Back-Stack.md
- Archivkopie dieser Datei: docs/_archive/06_Frontend/Navigation_V2_Screen-Baum_und_Back-Stack.md
Hinweis:
- Die V3Doku spiegelt den jetzt gültigen, startfähigen Stand der DesktopApp wider (kein erzwungener Login/Ping im MVP, TabStacks, Drilldown Veranstaltung → Turnier → Bewerb → Abteilung).
- Falls du ältere Verweise auf diese Datei findest, bitte auf die V3Doku aktualisieren.

View File

@ -123,3 +123,4 @@ Quellen: `frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/deskt
- EventFirstWorkflow: `docs/02_Guides/Event-First-Workflow.md`
- Begriffe: `docs/03_Domain/01_Glossary/Ubiquitous_Language.md`
- Analyse/Begründung: `docs/06_Frontend/Reports/2026-04-02_Navigation_Versionierung_Analyse_V2_vs_V3.md`
- TenantKonzept (eine Veranstaltung = ein Tenant): `docs/01_Architecture/Reference/Tenant-Konzept_Eine-Veranstaltung-eine-Datenbank.md`