Files
meldestelle/.junie/guidelines/technology-guides/web-app-guideline.md
T
StefanMo 95fe3e0573 chore(ci): Align GH Workflows with Docker SSoT, new paths; minimal SSoT guard; staticAnalysis (#23)
* chore(MP-21): snapshot pre-refactor state (Epic 1)

* chore(MP-22): scaffold new repo structure, relocate Docker Compose, move frontend/backend modules, update Makefile; add docs mapping and env template

* MP-22 Epic 2: Erfolgreich umgesetzt und verifiziert

* MP-23 Epic 3: Gradle/Build Governance zentralisieren

* MP-23 Epic 3: Gradle/Build Governance zentralisieren

* chore(devops)!: Docker-SSoT (.env) konsolidiert, Compose-Mounts ergänzt, Makefile entfernt

- ENV Single Source of Truth
  - docker/.env.example neu (inkl. REDIS_PASSWORD, Ports, Build-Overrides)
  - config/.env(.example) als DEPRECATED markiert (Verweis auf docker/.env[.example])

- Docker Compose vereinheitlicht (docker/docker-compose.yaml)
  - Postgres: zentralen postgresql.conf mounten (../config/postgres/postgresql.conf)
    und Start mit -c config_file=/etc/postgresql/postgresql.conf
  - Redis: zentralen redis.conf mounten (../config/redis/redis.conf)
    und Start via "redis-server … ${REDIS_PASSWORD:+--requirepass $REDIS_PASSWORD}"
  - Web-Nginx: ../config/nginx/nginx.prod.conf → /etc/nginx/nginx.conf (ro)
  - Monitoring: Prometheus/Grafana nutzen ../config/monitoring/* als SSoT

- Frontend/DI/Network (MP-23 Grundlage)
  - :frontend:core:network Modul mit Koin `apiClient` (Ktor + JSON/Retry/Timeout/Logging)
  - Plattform-Basis-URL-Auflösung (JVM: ENV API_BASE_URL; JS: globalThis.API_BASE_URL / Same-Origin)
  - Web index.html setzt API_BASE_URL (Query `?apiBaseUrl=…` > Same-Origin > Fallback)

- Build/Gradle & Module-Refs
  - settings.gradle.kts: neue Frontend-/Backend-Pfade bereits inkludiert
  - Features/Shell: Abhängigkeiten auf :frontend:shared / :frontend:core:* angepasst
  - Ping-API-Refs auf :backend:services:ping:ping-api vereinheitlicht

- Dockerfiles angepasst
  - backend/infrastructure/gateway/Dockerfile → Tasks/Pfade auf :backend:gateway
  - backend/services/ping/Dockerfile → Tasks/Pfade auf :backend:services:ping:ping-service

- Static Analysis / Guards
  - config/detekt/detekt.yml hinzugefügt
  - Leichter Arch-Guard (Frontend) gegen manuelle Authorization-Header vorbereitet

- Doku
  - docs/ARCHITECTURE.md (Struktur, Mapping, Next Steps) ergänzt
  - docs/adr/README.md angelegt

BREAKING CHANGES:
- Makefile komplett entfernt (bitte direkt `docker compose` verwenden)
- ENV-Quelle ist jetzt docker/.env (statt config/.env oder Root)
- Compose-Datei unter docker/docker-compose.yaml (nicht mehr compose.yaml im Repo-Root)

Verifikation (lokal):
- ENV anlegen: `cp docker/.env.example docker/.env` (Werte anpassen)
- Compose prüfen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml config`
- Infrastruktur: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle up -d postgres redis keycloak web-app`
- Services bauen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle build api-gateway ping-service --no-cache --progress=plain`

Refs: MP-22 (Epic 2), MP-23 (Epic 3)

* chore(devops)!: Docker-SSoT (.env) konsolidiert, Compose-Mounts ergänzt, Makefile entfernt

- ENV Single Source of Truth
  - docker/.env.example neu (inkl. REDIS_PASSWORD, Ports, Build-Overrides)
  - config/.env(.example) als DEPRECATED markiert (Verweis auf docker/.env[.example])

- Docker Compose vereinheitlicht (docker/docker-compose.yaml)
  - Postgres: zentralen postgresql.conf mounten (../config/postgres/postgresql.conf)
    und Start mit -c config_file=/etc/postgresql/postgresql.conf
  - Redis: zentralen redis.conf mounten (../config/redis/redis.conf)
    und Start via "redis-server … ${REDIS_PASSWORD:+--requirepass $REDIS_PASSWORD}"
  - Web-Nginx: ../config/nginx/nginx.prod.conf → /etc/nginx/nginx.conf (ro)
  - Monitoring: Prometheus/Grafana nutzen ../config/monitoring/* als SSoT

- Frontend/DI/Network (MP-23 Grundlage)
  - :frontend:core:network Modul mit Koin `apiClient` (Ktor + JSON/Retry/Timeout/Logging)
  - Plattform-Basis-URL-Auflösung (JVM: ENV API_BASE_URL; JS: globalThis.API_BASE_URL / Same-Origin)
  - Web index.html setzt API_BASE_URL (Query `?apiBaseUrl=…` > Same-Origin > Fallback)

- Build/Gradle & Module-Refs
  - settings.gradle.kts: neue Frontend-/Backend-Pfade bereits inkludiert
  - Features/Shell: Abhängigkeiten auf :frontend:shared / :frontend:core:* angepasst
  - Ping-API-Refs auf :backend:services:ping:ping-api vereinheitlicht

- Dockerfiles angepasst
  - backend/infrastructure/gateway/Dockerfile → Tasks/Pfade auf :backend:gateway
  - backend/services/ping/Dockerfile → Tasks/Pfade auf :backend:services:ping:ping-service

- Static Analysis / Guards
  - config/detekt/detekt.yml hinzugefügt
  - Leichter Arch-Guard (Frontend) gegen manuelle Authorization-Header vorbereitet

- Doku
  - docs/ARCHITECTURE.md (Struktur, Mapping, Next Steps) ergänzt
  - docs/adr/README.md angelegt

BREAKING CHANGES:
- Makefile komplett entfernt (bitte direkt `docker compose` verwenden)
- ENV-Quelle ist jetzt docker/.env (statt config/.env oder Root)
- Compose-Datei unter docker/docker-compose.yaml (nicht mehr compose.yaml im Repo-Root)

Verifikation (lokal):
- ENV anlegen: `cp docker/.env.example docker/.env` (Werte anpassen)
- Compose prüfen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml config`
- Infrastruktur: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle up -d postgres redis keycloak web-app`
- Services bauen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle build api-gateway ping-service --no-cache --progress=plain`

Refs: MP-22 (Epic 2), MP-23 (Epic 3)

* chore(devops)!: Docker-SSoT (.env) konsolidiert, Compose-Mounts ergänzt, Makefile entfernt

- ENV Single Source of Truth
  - docker/.env.example neu (inkl. REDIS_PASSWORD, Ports, Build-Overrides)
  - config/.env(.example) als DEPRECATED markiert (Verweis auf docker/.env[.example])

- Docker Compose vereinheitlicht (docker/docker-compose.yaml)
  - Postgres: zentralen postgresql.conf mounten (../config/postgres/postgresql.conf)
    und Start mit -c config_file=/etc/postgresql/postgresql.conf
  - Redis: zentralen redis.conf mounten (../config/redis/redis.conf)
    und Start via "redis-server … ${REDIS_PASSWORD:+--requirepass $REDIS_PASSWORD}"
  - Web-Nginx: ../config/nginx/nginx.prod.conf → /etc/nginx/nginx.conf (ro)
  - Monitoring: Prometheus/Grafana nutzen ../config/monitoring/* als SSoT

- Frontend/DI/Network (MP-23 Grundlage)
  - :frontend:core:network Modul mit Koin `apiClient` (Ktor + JSON/Retry/Timeout/Logging)
  - Plattform-Basis-URL-Auflösung (JVM: ENV API_BASE_URL; JS: globalThis.API_BASE_URL / Same-Origin)
  - Web index.html setzt API_BASE_URL (Query `?apiBaseUrl=…` > Same-Origin > Fallback)

- Build/Gradle & Module-Refs
  - settings.gradle.kts: neue Frontend-/Backend-Pfade bereits inkludiert
  - Features/Shell: Abhängigkeiten auf :frontend:shared / :frontend:core:* angepasst
  - Ping-API-Refs auf :backend:services:ping:ping-api vereinheitlicht

- Dockerfiles angepasst
  - backend/infrastructure/gateway/Dockerfile → Tasks/Pfade auf :backend:gateway
  - backend/services/ping/Dockerfile → Tasks/Pfade auf :backend:services:ping:ping-service

- Static Analysis / Guards
  - config/detekt/detekt.yml hinzugefügt
  - Leichter Arch-Guard (Frontend) gegen manuelle Authorization-Header vorbereitet

- Doku
  - docs/ARCHITECTURE.md (Struktur, Mapping, Next Steps) ergänzt
  - docs/adr/README.md angelegt

BREAKING CHANGES:
- Makefile komplett entfernt (bitte direkt `docker compose` verwenden)
- ENV-Quelle ist jetzt docker/.env (statt config/.env oder Root)
- Compose-Datei unter docker/docker-compose.yaml (nicht mehr compose.yaml im Repo-Root)

Verifikation (lokal):
- ENV anlegen: `cp docker/.env.example docker/.env` (Werte anpassen)
- Compose prüfen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml config`
- Infrastruktur: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle up -d postgres redis keycloak web-app`
- Services bauen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle build api-gateway ping-service --no-cache --progress=plain`

Refs: MP-22 (Epic 2), MP-23 (Epic 3)

* chore(ci): Workflows an Docker-SSoT & neue Struktur angepasst, minimaler SSoT-Guard

- ssot-guard.yml: Option B (minimal) → `docker compose -f docker/docker-compose.yaml config` als Lint
- integration-tests.yml: `./gradlew staticAnalysis` vor Integrationstests
- docs-kdoc-sync.yml: Dokka-Task Fallback (dokkaGfmAll || dokkaGfm), YouTrack-Sync nur wenn Script vorhanden
- deploy-proxmox.yml: Compose-Pfade auf docker/docker-compose.yaml + `--env-file docker/.env`; Build/Test Schritte vereinheitlicht
- ci-main.yml: SSoT-Skripte per `if: hashFiles(...)` guarded, Compose-Lint Fallback; OpenAPI‑Pfad → backend/gateway; ADR‑Pfade → docs/adr/**; `staticAnalysis` in Build integriert
- youtrack-sync.yml: unverändert (funktional)

Refs: MP-22, MP-23

* chore(ci): Workflows an Docker-SSoT & neue Struktur angepasst, minimaler SSoT-Guard

- ssot-guard.yml: Option B (minimal) → `docker compose -f docker/docker-compose.yaml config` als Lint
- integration-tests.yml: `./gradlew staticAnalysis` vor Integrationstests
- docs-kdoc-sync.yml: Dokka-Task Fallback (dokkaGfmAll || dokkaGfm), YouTrack-Sync nur wenn Script vorhanden
- deploy-proxmox.yml: Compose-Pfade auf docker/docker-compose.yaml + `--env-file docker/.env`; Build/Test Schritte vereinheitlicht
- ci-main.yml: SSoT-Skripte per `if: hashFiles(...)` guarded, Compose-Lint Fallback; OpenAPI‑Pfad → backend/gateway; ADR‑Pfade → docs/adr/**; `staticAnalysis` in Build integriert
- youtrack-sync.yml: unverändert (funktional)

Refs: MP-22, MP-23

* fix(ci): create .env from example before validating compose config

* fix(ci): update ssot-guard filename (.yaml) and sync workflow state

* fixing

* fix(webpack): correct sql.js fallback configuration for webpack 5
2025-12-03 12:03:40 +01:00

9.9 KiB

Client-App-Richtlinie (Compose Multiplatform)


guideline_type: "technology" scope: "web-app-multiplatform" audience: ["developers", "ai-assistants", "frontend-developers"] last_updated: "2025-09-15" dependencies: ["master-guideline.md", "project-standards/architecture-principles.md"] related_files: ["client/build.gradle.kts", "client/src/commonMain/", "client/src/wasmJsMain/", "client/src/jvmMain/"] ai_context: "Compose Multiplatform-Entwicklung, MVVM-Pattern, KMP-Architektur, Desktop- und Web-Client-Entwicklung"


1. Einleitung

Diese Richtlinie beschreibt die Architektur und die Best Practices für die Entwicklung der Client-Anwendungen für das "Meldestelle"-Projekt. Die Client-Anwendungen werden mit Compose Multiplatform für Desktop und Web entwickelt.

Das Hauptziel ist die maximale Wiederverwendung von Code zwischen den Desktop- und Web-Plattformen durch die konsequente Nutzung des commonMain-Source-Sets von Kotlin Multiplatform (KMP). Die Anwendung läuft sowohl als native Desktop-Anwendung (JVM) als auch als Web-Anwendung (WebAssembly).

🤖 AI-Assistant Hinweis: Compose Multiplatform Entwicklung folgt diesen Kernprinzipien:

  • commonMain: Geteilte UI-Logik und Business-Logic
  • MVVM-Pattern: ViewModels in commonMain, UI-Components plattformübergreifend
  • @Composable-Funktionen: Deklarative UI mit State Hoisting
  • expect/actual: Plattformspezifische Implementierungen nur wo nötig

2. Grundprinzipien

Deklarative UI mit Composable

Die gesamte Benutzeroberfläche wird als Baum von @Composable-Funktionen deklariert. Dies ist derselbe Ansatz, der auch bei Jetpack Compose für Android verwendet wird.

  • Zustandslosigkeit: Composable sollten bevorzugt zustandslos sein. Sie erhalten Daten als Parameter und geben Ereignisse über Lambda-Funktionen (Callbacks) nach oben weiter.
  • Wiederverwendbarkeit: Erstellen Sie kleine, spezialisierte und wiederverwendbare Composables. Vermeiden Sie monolithische UI-Funktionen.
  • Vorschau: Nutzen Sie @Preview-Annotationen (sofern von der IDE unterstützt), um UI-Komponenten isoliert zu entwickeln und zu visualisieren.

State Management

Der UI-Zustand (State) wird explizit verwaltet.

  • mutableStateOf und remember: Für einfachen, temporären UI-Zustand innerhalb einer Composable-Funktion.
  • State Hoisting: Der Zustand sollte so weit wie möglich nach oben in der Komponentenhierarchie verschoben werden ("State Hoisting"), idealerweise in eine ViewModel- oder Presenter-Klasse in commonMain.
  • ViewModels/Presenters: Komplexe Logik zur Zustandsverwaltung gehört in Klassen (z. B. ExampleViewModel) im commonMain-Modul. Diese Klassen sind plattformunabhängig und können von der UI (im jsMain-Modul) genutzt werden.

Styling

Das Styling erfolgt plattformspezifisch, aber mit gemeinsamen Prinzipien:

Gemeinsame Styling-Prinzipien (commonMain)

  • Compose Material Design: Nutzen Sie Material3-Komponenten und Theming für konsistente UI.
  • Gemeinsames Designsystem: Definieren Sie gemeinsame Farben, Typografie und Spacing in commonMain.
  • Responsive Design: Berücksichtigen Sie verschiedene Bildschirmgrößen (Desktop-Fenster vs. Browser-Viewports).

Web-spezifisches Styling (wasmJsMain)

  • CSS-Integration: Web-spezifische Styling-Anforderungen können über CSS in den Resources behandelt werden.
  • Browser-Kompatibilität: Berücksichtigen Sie Web-spezifische Rendering-Unterschiede.

Desktop-spezifisches Styling (jvmMain)

  • Native Look & Feel: Desktop-Anwendungen sollten sich nativ anfühlen.
  • Fenster-Management: Berücksichtigen Sie Desktop-spezifische UI-Patterns (Menüleisten, etc.).
// Beispiel für gemeinsames Theming in commonMain
@Composable
fun AppTheme(content: @Composable () -> Unit) {
    MaterialTheme(
        colorScheme = if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme(),
        typography = AppTypography,
        content = content
    )
}

Navigation

Die Navigation wird plattformunabhängig in commonMain implementiert:

  • ViewModel-basierte Navigation: Ein StateFlow oder mutableState im ViewModel repräsentiert die aktuelle Route/Screen.
  • Gemeinsamer Router: Ein zentraler Router-Composable in commonMain reagiert auf Zustandsänderungen und rendert den entsprechenden Screen.
  • Plattformspezifische Einstiegspunkte: Desktop und Web haben separate main.kt-Dateien, aber nutzen denselben gemeinsamen App-Composable.

3. Projekt- und Code-Struktur

Die Codebasis ist klar zwischen plattformunabhängiger Logik (commonMain) und plattformspezifischer Implementation (jvmMain, wasmJsMain) getrennt.

Source Sets

  • client/src/commonMain:

    • UI-Code: Alle @Composable-Funktionen, die zwischen Desktop und Web geteilt werden.
    • Business-Logik: ViewModels/Presenters, die den UI-Zustand verwalten.
    • Data-Klassen: Modelle, die Daten repräsentieren.
    • Common Dependencies: Shared Compose-Dependencies (runtime, foundation, material3, ui).
  • client/src/jvmMain (Desktop-Plattform):

    • main.kt: Der Einstiegspunkt der Desktop-Anwendung.
    • Desktop-spezifischer Code: Plattformspezifische Implementierungen und Integrationen.
    • Desktop Dependencies: compose.desktop.currentOs, Coroutines für Swing.
  • client/src/wasmJsMain (Web-Plattform):

    • main.kt: Der Einstiegspunkt der Web-Anwendung (WebAssembly).
    • Web-spezifischer Code: Browser-spezifische Implementierungen.
    • Platform-spezifische Implementierungen: Web-APIs und Browser-Integrationen.
  • client/src/wasmJsMain/resources:

    • index.html: Das Host-HTML-Dokument für die Compose-Anwendung.
    • Statische Assets: Bilder, Schriftarten und andere statische Dateien für die Web-Version.

Shared Module Integration

  • core/commonMain (oder äquivalente shared-Module):
    • Repositories/Services: Code für den Datenzugriff (z.B. Ktor-HTTP-Clients zum Aufrufen des Backends).
    • Business-Logik: Plattformunabhängige Geschäftslogik, die von allen Client-Plattformen genutzt wird.

4. Entwicklung und Ausführung

Desktop-Entwicklung

Für die Desktop-Anwendung stehen folgende Gradle-Tasks zur Verfügung:

# Desktop-Anwendung direkt ausführen
./gradlew :client:run

# Desktop-Distribution erstellen (DMG, MSI, DEB)
./gradlew :client:createDistributable
./gradlew :client:packageDmg        # macOS
./gradlew :client:packageMsi        # Windows
./gradlew :client:packageDeb        # Linux

Web-Entwicklung mit Hot-Reload

Für die Web-Anwendung mit automatischer Neuladung bei Änderungen:

# Web-App mit Hot-Reload starten
./gradlew :client:wasmJsBrowserDevelopmentRun

Docker-Setup für Web-Entwicklung

Das Docker-Setup ist spezifisch für die Web-Entwicklung konfiguriert (wie in README-DOCKER.md beschrieben):

# Startet die Web-App mit Hot-Reload
docker-compose -f docker-compose.yaml \
-f docker-compose.clients.yaml up -d web-app

Der Dienst ist dann unter dem in der docker-compose.clients.yml konfigurierten Port (z.B. Port 3000) erreichbar.

Produktions-Builds

Desktop-Distribution

# Erstellt native Distributionen für alle konfigurierten Plattformen
./gradlew :client:packageDistributionForCurrentOS

Web-Distribution

# Erstellt optimierte WebAssembly-Artefakte für die Produktion
./gradlew :client:wasmJsBrowserDistribution

Das Docker-Image für die Web-Produktion (Dockerfile im client-Verzeichnis) sollte den wasmJsBrowserDistribution-Task nutzen, um die finalen Artefakte zu bauen.

5. Plattformspezifische Besonderheiten

Desktop (jvmMain)

  • Fenster-Management: Nutzen Sie Compose Desktop-APIs für Fensteroperationen.
  • System-Integration: Zugriff auf Desktop-spezifische Features (Dateisystem, Notifications, etc.).
  • Performance: Desktop-Apps können mehr Ressourcen nutzen als Web-Apps.

Web (wasmJsMain)

  • Browser-APIs: Zugriff auf Web-APIs erfolgt über external-Deklarationen.
  • Bundle-Size: Achten Sie auf die Größe der WebAssembly-Bundles für optimale Ladezeiten.
  • SEO und Accessibility: Berücksichtigen Sie Web-spezifische Anforderungen.

6. Dos and Don'ts

Multiplatform Best Practices

  • DO: Die gesamte UI-Logik (State-Management, Datenabruf, Validierung) in commonMain implementieren.
  • DO: Kleine wiederverwendbare und zustandslose Composable in commonMain erstellen.
  • DO: Material3 und gemeinsames Theming für konsistente UI zwischen Plattformen verwenden.
  • DO: Events von der UI über Lambda-Funktionen an die ViewModels in commonMain weiterleiten.
  • DO: Plattformspezifische Features über expect/actual-Mechanismus abstrahieren.

Platform-Specific Guidelines

  • DO (Desktop): Native Look & Feel und Desktop-UI-Patterns verwenden.
  • DO (Web): Web-Standards und Accessibility-Guidelines befolgen.

Don'ts

  • DON'T: Geschäftslogik, API-Aufrufe oder komplexe Zustandsmanipulationen direkt in @Composable-Funktionen schreiben.
  • DON'T: Plattformspezifische Code direkt in commonMain verwenden ohne expect/actual.
  • DON'T (Web): Den DOM direkt manipulieren. Compose Multiplatform verwaltet das Rendering. Falls Interaktion mit externen Bibliotheken nötig ist, nutzen Sie external-Mechanismen sauber gekapselt.
  • DON'T: Annahmen über die Zielplattform in commonMain machen.

Navigation: