meldestelle/docs/01_Architecture/Reference/Tenant-Konzept_Eine-Veranstaltung-eine-Datenbank.md
StefanMoCoAt 85282ea7b4
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
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.
2026-04-02 23:17:07 +02:00

115 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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
---
# TenantKonzept — Eine Veranstaltung = eine Datenbank (ein Tenant)
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) 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: „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) Was bedeutet das fürs DatenbankSchema?
- 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, DSGVOkonforme 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 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).
Fehlerbilder (vereinheitlicht):
- Unbekannte Veranstaltung → `404 Unknown event`.
- Veranstaltung gesperrt/archiviert → `423 Locked` (oder 403 je EndpointPolicy).
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) 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.
---
## 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`