docs: massive restructuring of documentation, development guides and agent playbooks
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
---
|
||||
type: Guide
|
||||
status: ACTIVE
|
||||
owner: Lead Architect
|
||||
tags: [coding-style, kdoc, documentation]
|
||||
last_update: 2026-03-15
|
||||
---
|
||||
|
||||
# KDoc-Styleguide (Kurzfassung)
|
||||
|
||||
Dieser Styleguide definiert die wichtigsten Regeln für KDoc-Kommentare in Kotlin-Projekten der Meldestelle. Ziel:
|
||||
Verständliche, konsistente API-Dokumentation via Dokka (GFM/HTML).
|
||||
|
||||
## Grundregeln
|
||||
|
||||
- Sprache: Deutsch für Fließtexte; Code/Bezeichner bleiben Englisch.
|
||||
- Jeder public class, interface, object, enum, public function und public property erhält einen KDoc-Block.
|
||||
- KDoc beginnt mit einem vollständigen, aussagekräftigen Satz in der dritten Person.
|
||||
- Beispiele und wichtige Hinweise als kurze Absätze oder Listen, keine Romane.
|
||||
|
||||
## Struktur eines KDoc-Blocks
|
||||
|
||||
```kotlin
|
||||
/**
|
||||
* Beschreibt prägnant, was das Element macht und warum es existiert.
|
||||
*
|
||||
* Details: Optionale Erläuterung von Parametern, Nebenwirkungen, Fehlerfällen.
|
||||
*
|
||||
* @param id Eindeutige Kennung des Members
|
||||
* @return Das gefundene Objekt oder null, wenn nicht vorhanden
|
||||
* @throws IllegalArgumentException Falls Parameter ungültig sind
|
||||
*/
|
||||
fun findMember(id: MemberId): Member?
|
||||
```
|
||||
|
||||
## Tags
|
||||
|
||||
- @param: Für jeden Parameter bei public Funktionen
|
||||
- @return: Wenn Rückgabewert semantisch relevant ist
|
||||
- @throws: Relevante Exceptions dokumentieren
|
||||
- @since, @see: Sparsam verwenden, wenn es wirklichen Mehrwert bringt
|
||||
|
||||
## Stil & Sprache
|
||||
|
||||
- Klar, knapp, aktiv. Keine Redundanz.
|
||||
- Domänenbegriffe verwenden (BCs: members, horses, events, masterdata, infrastructure).
|
||||
- Keine Interna oder Secrets dokumentieren.
|
||||
|
||||
## Beispielschnipsel
|
||||
|
||||
```kotlin
|
||||
/** Erstellt einen neuen Event und persistiert ihn transaktional. */
|
||||
fun createEvent(cmd: CreateEventCommand): EventId
|
||||
```
|
||||
|
||||
## Dokka-Hinweise
|
||||
|
||||
- Dokka erzeugt GFM (Markdown) unter build/dokka/gfm und HTML unter build/dokka/html.
|
||||
- Source-Link führt auf GitHub (main-Branch). Prüfe Links in der CI.
|
||||
|
||||
## Review
|
||||
|
||||
- PR-Checklist: "KDoc vollständig?" anhaken, wenn neue public APIs hinzugekommen sind.
|
||||
- Vale/markdownlint gelten nur für .md; KDoc wird redaktionell in Code-Reviews geprüft.
|
||||
@@ -0,0 +1,92 @@
|
||||
# 🛠️ Guide: Conveyor Installation
|
||||
|
||||
Dieses Dokument beschreibt die Installation von **Hydraulic Conveyor** auf verschiedenen Linux-Distributionen (Ubuntu
|
||||
26.04 und Fedora 44).
|
||||
|
||||
---
|
||||
|
||||
## 1. Ubuntu 26.04 (Debian-basiert)
|
||||
|
||||
Der am einfachsten Weg für Ubuntu ist der direkte Download des `.deb`-Pakets. Dieses konfiguriert bei der Installation
|
||||
automatisch das APT-Repository für zukünftige Updates.
|
||||
|
||||
### Installation via .deb (Empfohlen)
|
||||
|
||||
```bash
|
||||
# Aktuelles Paket herunterladen (Beispiel v12.0 - bitte Version ggf. anpassen)
|
||||
VERSION="12.0"
|
||||
curl -L https://downloads.hydraulic.dev/conveyor/conveyor_${VERSION}_amd64.deb -o conveyor.deb
|
||||
|
||||
# Installieren (konfiguriert auch das Repo automatisch)
|
||||
sudo apt update
|
||||
sudo apt install ./conveyor.deb
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Fedora 44 (RPM-basiert)
|
||||
|
||||
Für Fedora wird die Installation via Tarball empfohlen, da Conveyor als autarkes Binary geliefert wird.
|
||||
|
||||
### Installation via Tarball (Systemweit)
|
||||
|
||||
Dies ist der zuverlässigste Weg für Fedora:
|
||||
|
||||
```bash
|
||||
# Version definieren (Beispiel v12.0, bitte aktuelle Version prüfen)
|
||||
VERSION="12.0"
|
||||
curl -L https://downloads.hydraulic.dev/conveyor/conveyor-${VERSION}-linux-amd64.tar.gz -o conveyor.tar.gz
|
||||
|
||||
# Entpacken nach /opt
|
||||
sudo tar -xzf conveyor.tar.gz -C /opt/
|
||||
sudo ln -s /opt/conveyor-${VERSION}/bin/conveyor /usr/local/bin/conveyor
|
||||
|
||||
# Test
|
||||
conveyor --version
|
||||
```
|
||||
|
||||
### Installation via RPM (Falls verfügbar)
|
||||
|
||||
Prüfen Sie auf der Hydraulic Website, ob mittlerweile ein natives RPM-Repository existiert. Falls ja:
|
||||
|
||||
```bash
|
||||
sudo dnf config-manager --add-repo https://conveyor.hydraulic.dev/rpm/conveyor.repo
|
||||
sudo dnf install conveyor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Post-Installation & Verifikation
|
||||
|
||||
Nach der Installation sollten Sie den Pfad und die Version prüfen:
|
||||
|
||||
```bash
|
||||
conveyor --version
|
||||
```
|
||||
|
||||
### Root-Key Initialisierung
|
||||
|
||||
Beim ersten Ausführen von `conveyor` wird ein Root-Key generiert. **Sichern Sie diesen unbedingt!**
|
||||
|
||||
```bash
|
||||
conveyor make site
|
||||
```
|
||||
|
||||
*Folgen Sie den Anweisungen im Terminal zur Sicherung des Root-Keys.*
|
||||
|
||||
---
|
||||
|
||||
## 4. Troubleshooting
|
||||
|
||||
### Fehlende Bibliotheken (Fedora)
|
||||
|
||||
Falls Conveyor native Hilfe benötigt (z.B. für Icons oder Kompression):
|
||||
|
||||
```bash
|
||||
sudo dnf install libX11 libXext libXrender
|
||||
```
|
||||
|
||||
### Berechtigungen
|
||||
|
||||
Stellen Sie sicher, dass Ihr Benutzer in der Gruppe `docker` ist, falls Sie Conveyor innerhalb von Containern nutzen
|
||||
oder Docker-basierte Inputs verwenden (für dieses Projekt primär lokal relevant).
|
||||
@@ -0,0 +1,112 @@
|
||||
# 📦 Guide: Desktop App Packaging (Conveyor & Gradle)
|
||||
|
||||
Dieses Dokument beschreibt den professionellen Packaging-Prozess für die Meldestelle Desktop App. Wir nutzen **Conveyor** als primäres Werkzeug für das Cross-Platform Packaging (Windows, Linux, macOS), da es stabile Installer inklusive signierter Updates und gebündelter JREs erzeugt.
|
||||
|
||||
---
|
||||
|
||||
## 1. Strategie: Conveyor vs. Gradle
|
||||
|
||||
| Feature | Conveyor (Empfohlen) | Gradle (Compose Plugin) |
|
||||
| :--- | :--- | :--- |
|
||||
| **Zielgruppe** | Endanwender (Produktion) | Entwickler (Lokaler Test) |
|
||||
| **Plattformen** | Windows (.msix), Linux (.deb), macOS | Nur Host-OS (Linux auf Linux) |
|
||||
| **Updates** | Automatisch integriert | Manuell |
|
||||
| **JRE** | Amazon Corretto (isoliert) | System JRE oder Toolchain |
|
||||
|
||||
---
|
||||
|
||||
## 2. Cross-Packaging mit Conveyor
|
||||
|
||||
Conveyor ist so konfiguriert, dass es von Linux aus Pakete für alle Zielsysteme schnüren kann.
|
||||
|
||||
### Voraussetzungen
|
||||
1. **JAR-Dateien:** Die App muss kompiliert sein:
|
||||
```bash
|
||||
./gradlew :frontend:shells:meldestelle-desktop:jvmJar
|
||||
```
|
||||
2. **Icons:** Das System sucht nach `icon.png` in `frontend/shells/meldestelle-desktop/src/jvmMain/resources/`.
|
||||
|
||||
### Pakete bauen
|
||||
Führen Sie Conveyor im Projekt-Root aus:
|
||||
|
||||
```bash
|
||||
# Komplette Release-Site (Windows & Linux)
|
||||
conveyor make site
|
||||
|
||||
# Nur ein spezifisches Paket (schneller für Tests)
|
||||
conveyor make debian-package # Linux .deb
|
||||
conveyor make windows-msix # Windows .msix
|
||||
```
|
||||
|
||||
Die Ergebnisse liegen im Ordner `output/`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Konfiguration (`conveyor.conf`)
|
||||
|
||||
Wichtige Parameter der aktuellen Konfiguration (v1.0.1):
|
||||
* **JDK:** Nutzt `Amazon Corretto 21` für maximale Cross-Platform Stabilität.
|
||||
* **Heap-Size:** Erhöht auf `-Xmx1024m`, um auch große Stammdaten-Importe zu bewältigen.
|
||||
* **Linux-Deps:** Automatische Installation von `libasound2`, `libgl1-mesa-glx` und `libx11-6`.
|
||||
* **Native Access:** `--enable-native-access=ALL-UNNAMED` ist für Netty/SQLite aktiviert.
|
||||
|
||||
---
|
||||
|
||||
## 4. Netzwerk & Sicherheit (WICHTIG)
|
||||
|
||||
Damit die P2P-Funktionen (Chat, Discovery, Sync) nach der Installation funktionieren, müssen folgende Ports auf dem Host-System offen sein:
|
||||
|
||||
| Port | Protokoll | Funktion |
|
||||
| :--- | :--- | :--- |
|
||||
| **8080** | TCP | P2P Sync & Datenabgleich |
|
||||
| **8090** | TCP | Veranstaltungs-Chat (WebSocket) |
|
||||
| **5353** | UDP | mDNS Discovery (Geräte finden) |
|
||||
|
||||
### Firewall-Einrichtung (Linux)
|
||||
Nutzen Sie das optimierte Setup-Script:
|
||||
```bash
|
||||
sudo ./setup-firewall-linux.sh
|
||||
```
|
||||
|
||||
### Windows-Besonderheit
|
||||
Beim ersten Start der `.msix` App wird Windows fragen, ob der Netzwerkzugriff erlaubt werden soll. **Wichtig:** Sowohl "Private" als auch "Öffentliche" Netzwerke anhaken, falls auf Turnieren oft Gast-WLANs oder Hotspots genutzt werden.
|
||||
|
||||
---
|
||||
|
||||
## 5. Troubleshooting
|
||||
|
||||
### Problem: "No main class specified"
|
||||
**Lösung:** Stellen Sie sicher, dass in der `Main.kt` eine saubere Top-Level `fun main()` existiert und in der `conveyor.conf` auf `at.mocode.frontend.shell.desktop.MainKt` verwiesen wird.
|
||||
|
||||
### Problem: SQLite / Native Libs laden nicht
|
||||
**Lösung:** Prüfen Sie, ob `extract-native-libraries.conf` in der `conveyor.conf` inkludiert ist.
|
||||
|
||||
### Problem: JmDNS findet keine Teilnehmer
|
||||
**Lösung:** Prüfen Sie die Ports via `ss -tulpn`. Auf Linux blockieren oft Docker-Interfaces (`br-*`) den Broadcast. Die App filtert diese nun automatisch, aber ein aktives `setup-firewall-linux.sh` ist zwingend erforderlich.
|
||||
|
||||
## 6. Performance-Optimierung (Gradle)
|
||||
|
||||
Der Build-Prozess kann bei aktivierter Web-Kompilierung (WASM/JS) sehr lange dauern. Für die reine Desktop-Entwicklung
|
||||
wurde WASM standardmäßig deaktiviert.
|
||||
|
||||
* **WASM aktivieren (z.B. für CI/Portal):** `./gradlew -PenableWasm=true ...`
|
||||
* **WASM deaktivieren (Default):** `./gradlew ...` (Spart bis zu 70% Build-Zeit).
|
||||
|
||||
## 7. Gradle Deep-Optimierung
|
||||
|
||||
Neben dem Deaktivieren von WASM wurden folgende systemweite Optimierungen in der `gradle.properties` vorgenommen:
|
||||
|
||||
* **Configuration Cache:** Aktiviert. Gradle merkt sich die Projektstruktur, was den Start jedes Befehls um Sekunden bis
|
||||
Minuten verkürzt.
|
||||
* **JVM G1GC & 12GB Heap:** Optimiert für große Multi-Modul-Projekte auf Systemen mit viel RAM (ab 16GB).
|
||||
* **Parallel Workers:** Erhöht auf 12, um die 16 logischen Kerne Ihres Rechners besser auszulasten.
|
||||
|
||||
### Optionale Analysen
|
||||
|
||||
Statische Analysen sind nun standardmäßig **deaktiviert**, um den täglichen Workflow nicht zu bremsen.
|
||||
|
||||
* **Analyse laufen lassen:** `./gradlew staticAnalysis -PrunStaticAnalysis=true`
|
||||
* **Dokka Dokumentation bauen:** `./gradlew dokkaAll -PrunDokka=true`
|
||||
|
||||
Stellen Sie in der `gradle.properties` sicher, dass `enableWasm=false` gesetzt ist, wenn Sie primär an der Desktop-App
|
||||
arbeiten.
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
type: Guide
|
||||
status: ACTIVE
|
||||
owner: 🧹 Curator & 🏗️ Lead Architect
|
||||
last_update: 2026-04-02
|
||||
sources:
|
||||
- docs/03_Domain/01_Glossary/Ubiquitous_Language.md
|
||||
- Domain Workshop 2026-03-17
|
||||
---
|
||||
|
||||
# Event‑First‑Workflow (MVP)
|
||||
|
||||
Ziel: Ein neuer Veranstaltungs‑Durchlauf wird konsequent „Event‑First“ aufgebaut. Dabei folgt der Bedienfluss strikt der Domänen‑Hierarchie:
|
||||
|
||||
Veranstaltung → Turnier → Bewerbe → Abteilungen → Startliste
|
||||
|
||||
---
|
||||
|
||||
## 1. Vorbedingungen
|
||||
|
||||
- Verein (→ Begriff: Veranstalter) ist im System vorhanden.
|
||||
- Grundlegende Stammdaten synchron (Reiter, Pferde, Vereine) – optional für die Planung, erforderlich für Startlisten.
|
||||
|
||||
Querverweis: → Ubiquitous Language, Abschnitt „Hierarchie der Veranstaltungs‑Struktur“ und Begriffe „Veranstaltung“, „Turnier“, „Bewerb“, „Abteilung“, „Startliste“.
|
||||
|
||||
---
|
||||
|
||||
## 2. Schritt‑für‑Schritt
|
||||
|
||||
1) Veranstaltung anlegen
|
||||
- Eingaben: Titel, Datum(e), Ort, Typ (Turnier, Reitertreffen, …), Veranstalter (Vereinsnummer), interne Event‑ID (System vergibt).
|
||||
- Output: Veranstaltung existiert, → Veranstaltungs‑Kassa und → TeilnehmerKonto‑Container werden vorbereitet.
|
||||
|
||||
2) Turnier anlegen (innerhalb der Veranstaltung; mehrfach möglich)
|
||||
- Eingaben: Turniernummer (offiziell, wenn vorhanden), Sparte(n) (z. B. CDN, CSN), Kategorie (C‑NEU, C, …), geplanter Zeitraum.
|
||||
- Output: Turnier angelegt, → Turnierkassa eröffnet; Ausschreibungs‑Metadaten vorbereiten.
|
||||
|
||||
3) Bewerbe anlegen (pro Turnier)
|
||||
- Eingaben: fortlaufende Bewerbsnummer, Bezeichnung, Klasse/Höhe, Richtverfahren, Startberechtigungen.
|
||||
- Output: Bewerbe als Container für Abteilungen vorhanden.
|
||||
|
||||
4) Abteilungen planen/anlegen (pro Bewerb, mindestens eine)
|
||||
- Eingaben: Abteilungsnummer (fortlaufend), Abteilungs‑Typ: `SEPARATE_SIEGEREHRUNG` oder `ORGANISATORISCH`, optional Teilnehmerkreis‑Filter (Lizenz, Altersklasse …).
|
||||
- Systemhinweis: Bei Überschreiten von ÖTO‑Schwellenwerten zeigt das System WARNUNG + Option „Override“ (→ TBA hat letztes Wort).
|
||||
- Output: Abteilungen stehen für Nennungen/Startlisten bereit.
|
||||
|
||||
5) Startliste erzeugen (pro Abteilung)
|
||||
- Eingaben: Nennungen (Paar Reiter+Pferd), Reihenfolgen‑Logik (z. B. Zufall, Startwunsch), Kollisionen prüfen.
|
||||
- Output: Fixierte Startliste je Abteilung; Grundlage für Ergebniserfassung und Abrechnung (Sportförderbeitrag, Tierwohl‑Euro pro Start).
|
||||
|
||||
---
|
||||
|
||||
## 3. Rollen & Verantwortungen
|
||||
|
||||
- Meldestelle: Erfassung/Prüfung der Daten, Startlistenpflege, Kassenabwicklung.
|
||||
- TBA (Turnierbeauftragter): Genehmigung von Abteilungs‑Overrides und Regel‑Abweichungen (Override‑Event wird protokolliert).
|
||||
- Veranstalter: Finanzielle Verantwortung, Kassen‑Schluss, Freigabe der Ausschreibung.
|
||||
|
||||
---
|
||||
|
||||
## 4. Artefakte & Systemobjekte
|
||||
|
||||
- Veranstaltung (Root) → Veranstaltungs‑Kassa, TeilnehmerKonto‑Container, Multi‑Turnier‑Verrechnung.
|
||||
- Turnier → Turnierkassa, Ausschreibung.
|
||||
- Bewerb → Liste von Abteilungen.
|
||||
- Abteilung → kleinste ausführbare Einheit (Nennungen, Startliste, Ergebnis, Siegerehrung nach Typ).
|
||||
|
||||
---
|
||||
|
||||
## 5. Akzeptanzkriterien (MVP)
|
||||
|
||||
- Erstellung in exakt der Reihenfolge möglich; Zwischenspeichern und spätere Fortsetzung unterstützt.
|
||||
- Abteilungen mindestens 1 pro Bewerb; Typ wählbar; Warnungen bei ÖTO‑Schwellenwert‑Überschreitung.
|
||||
- Startliste pro Abteilung generierbar; Kollisionen und Startwünsche werden berücksichtigt.
|
||||
- Abrechnung: Gebühren pro Start (Sportförderbeitrag, Tierwohl‑Euro) werden korrekt ausgewiesen; Zahlvorgänge können turnierübergreifend auf TeilnehmerKonto verbucht werden (Event‑Ebene).
|
||||
|
||||
---
|
||||
|
||||
## 6. Querverweise
|
||||
|
||||
- Domänenbegriffe: `docs/03_Domain/01_Glossary/Ubiquitous_Language.md`
|
||||
- ÖTO‑Schwellenwerte: `docs/03_Domain/02_Reference/OETO_Regelwerk/Abteilungs-Trennungs-Schwellenwerte.md`
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
type: Guide
|
||||
status: ACTIVE
|
||||
owner: Lead Architect
|
||||
last_update: 2026-04-28
|
||||
---
|
||||
|
||||
# Git Branching & Deployment Strategy (Meldestelle)
|
||||
|
||||
Um parallele Weiterentwicklung und stabile Feld-Tests zu ermöglichen, nutzen wir einen vereinfachten **GitHub Flow** mit Release-Tags. Da wir ein kleines Team (bzw. Solo-Entwickler mit KI-Agents) sind, verzichten wir auf übermäßig komplexe Git-Flow-Modelle (wie `develop`, `release/*`, `hotfix/*`), stellen aber Stabilität für Deployments sicher.
|
||||
|
||||
## 1. Branching-Struktur
|
||||
|
||||
### `main` (Source of Truth / Production)
|
||||
* **Zweck:** Enthält *immer* den aktuellen, stabilen und im Feld getesteten/auslieferbaren Code.
|
||||
* **Regel:** Direkte Commits auf `main` sind tabu (außer Notfall-Hotfixes).
|
||||
* **Deployment:** Ein Push/Merge auf `main` bedeutet **nicht** zwingend ein sofortiges Deployment auf Zora, aber der Code ist *bereit* dafür.
|
||||
|
||||
### Feature Branches (`feature/*` oder `fix/*`)
|
||||
* **Zweck:** Hier findet die eigentliche Entwicklung statt (z.B. neue Bounded Contexts, Wizards).
|
||||
* **Namenskonvention:** `feature/event-wizard-neu`, `fix/zns-import-bug`
|
||||
* **Lebensdauer:** So kurz wie möglich. Sobald ein Feature/Fix *in sich geschlossen* und lokal getestet ist, wird ein Pull Request (PR) auf `main` erstellt.
|
||||
|
||||
### Release Tags (`v1.x.x`)
|
||||
* **Zweck:** Markiert einen spezifischen, stabilen Punkt auf dem `main`-Branch, der tatsächlich für ein Turnier (Feld-Test) deployed wurde.
|
||||
* **Szenario:** Du hast Version `v1.2.0` (Plan-B) für ein Turnier deployed. Du entwickelst weiter auf `feature/*` und mergest in `main`. Das nächste Turnier bekommt dann Tag `v1.3.0`.
|
||||
|
||||
## 2. Der Workflow im Alltag
|
||||
|
||||
1. **Start:** `git checkout main` -> `git pull` -> `git checkout -b feature/mein-neues-feature`
|
||||
2. **Entwicklung:** Arbeiten, KI-Agents nutzen, Commits machen.
|
||||
3. **Abschluss:** Feature ist fertig.
|
||||
4. **Merge:** Pull Request in Gitea erstellen (oder lokal: `git checkout main`, `git merge feature/mein-neues-feature`, `git push`).
|
||||
5. **Aufräumen:** `git branch -d feature/mein-neues-feature`
|
||||
|
||||
## 3. Strategie für Feld-Tests (Turnier-Einsatz)
|
||||
|
||||
Wenn ein Turnier ansteht und ein stabiler Stand eingefroren werden muss:
|
||||
|
||||
1. Stelle sicher, dass `main` den gewünschten Zustand hat.
|
||||
2. Setze einen Tag in Git: `git tag -a v1.2.0 -m "Release für Turnier in Neumarkt"`
|
||||
3. Pushe den Tag: `git push origin v1.2.0`
|
||||
4. **Deployment:** Das Deployment-Skript zieht sich *diesen* Tag auf Zora (oder baut den Docker-Container aus diesem Tag).
|
||||
|
||||
### Was passiert, wenn während des Turniers ein Bug auftritt (Hotfix)?
|
||||
|
||||
*Szenario: Das Turnier läuft auf `v1.2.0`. Auf `main` gibt es schon neuere Features (unfertig).*
|
||||
|
||||
1. Checkout des stabilen Tags: `git checkout -b hotfix/turnier-fix v1.2.0`
|
||||
2. Bug fixen, committen.
|
||||
3. Neuen Tag für das Deployment setzen: `git tag -a v1.2.1 -m "Hotfix ZNS Import"`
|
||||
4. `git push origin v1.2.1` -> Fix wird auf Zora deployed.
|
||||
5. **WICHTIG (Backport):** Damit der Fix nicht verloren geht, den Hotfix-Branch danach in `main` mergen: `git checkout main`, `git merge hotfix/turnier-fix`.
|
||||
|
||||
## 4. Gitea Actions (CI/CD)
|
||||
* **Pushes auf `feature/*`:** Führen Code-Checks/Tests aus.
|
||||
* **Pushes auf `main`:** Führen erweiterte Tests aus und bauen Docker-Images mit dem Tag `latest` sowie dem Git-SHA in die interne Registry (`10.0.0.22:3000`).
|
||||
* **Erstellung eines Tags (`v*`):** Triggert automatisch den Build und Push von Docker-Images in die interne Registry. Das Image erhält den Namen des Tags (z.B. `:v1.2.0`). Dies ist die Basis für stabile Deployments auf Zora.
|
||||
@@ -0,0 +1,455 @@
|
||||
---
|
||||
type: Guide
|
||||
status: ACTIVE
|
||||
owner: Frontend Expert
|
||||
last_update: 2026-03-15
|
||||
---
|
||||
# SQLDelight-Integration in Compose Multiplatform
|
||||
|
||||
Diese Anleitung zeigt, wie SQLDelight in einem Compose Multiplatform-Projekt mit Koin Dependency Injection integriert wird.
|
||||
|
||||
## Schritt 1: Abhängigkeiten hinzufügen
|
||||
|
||||
Folgende Abhängigkeiten in `gradle/libs.versions.toml` eintragen:
|
||||
|
||||
```toml
|
||||
[versions]
|
||||
sqldelight = "2.0.1"
|
||||
koin = "3.5.3"
|
||||
|
||||
[libraries]
|
||||
sqldelight-driver-sqlite = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" }
|
||||
sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
|
||||
sqldelight-driver-native = { module = "app.cash.sqldelight:native-driver", version.ref = "sqldelight" }
|
||||
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
||||
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
|
||||
koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" }
|
||||
|
||||
[plugins]
|
||||
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
|
||||
```
|
||||
|
||||
In `build.gradle.kts` (Projektebene):
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
alias(libs.plugins.sqldelight) apply false
|
||||
}
|
||||
```
|
||||
|
||||
In `shared/build.gradle.kts`:
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
alias(libs.plugins.sqldelight)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(libs.koin.core)
|
||||
implementation(libs.sqldelight.driver.sqlite)
|
||||
}
|
||||
|
||||
androidMain.dependencies {
|
||||
implementation(libs.koin.android)
|
||||
implementation(libs.sqldelight.driver.android)
|
||||
}
|
||||
|
||||
iosMain.dependencies {
|
||||
implementation(libs.sqldelight.driver.native)
|
||||
}
|
||||
|
||||
desktopMain.dependencies {
|
||||
implementation(libs.sqldelight.driver.sqlite)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sqldelight {
|
||||
databases {
|
||||
create("AppDatabase") {
|
||||
packageName.set("com.example.database")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Schritt 2: SQL-Schema erstellen
|
||||
|
||||
**Verzeichnisstruktur anlegen:**
|
||||
|
||||
`shared/src/commonMain/sqldelight/com/example/database/`
|
||||
|
||||
Datei `User.sq` erstellen:
|
||||
|
||||
```sql
|
||||
CREATE TABLE User
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
imageUrl TEXT
|
||||
);
|
||||
|
||||
-- Neuen Benutzer einfügen
|
||||
insertUser
|
||||
::
|
||||
INSERT INTO User(name, imageUrl)
|
||||
VALUES (?, ?);
|
||||
|
||||
-- Alle Benutzer abrufen
|
||||
getAllUsers
|
||||
::
|
||||
SELECT *
|
||||
FROM User;
|
||||
|
||||
-- Benutzer nach ID abrufen
|
||||
getUserById
|
||||
::
|
||||
SELECT *
|
||||
FROM User
|
||||
WHERE id = ?;
|
||||
|
||||
-- Benutzer aktualisieren
|
||||
updateUser
|
||||
::
|
||||
UPDATE User
|
||||
SET name = ?,
|
||||
imageUrl = ?
|
||||
WHERE id = ?;
|
||||
|
||||
-- Benutzer löschen
|
||||
deleteUser
|
||||
::
|
||||
DELETE
|
||||
FROM User
|
||||
WHERE id = ?;
|
||||
|
||||
-- Alle Benutzer löschen
|
||||
deleteAllUsers
|
||||
::
|
||||
DELETE
|
||||
FROM User;
|
||||
```
|
||||
|
||||
## Schritt 3: Datenbank-Treiber-Interface erstellen
|
||||
|
||||
In `shared/src/commonMain/kotlin/database/DatabaseDriverFactory.kt`:
|
||||
|
||||
```kotlin
|
||||
expect class DatabaseDriverFactory {
|
||||
fun createDriver(): SqlDriver
|
||||
}
|
||||
```
|
||||
|
||||
## Schritt 4: Plattformspezifische Implementierungen
|
||||
|
||||
### Android —
|
||||
|
||||
`shared/src/androidMain/kotlin/database/DatabaseDriverFactory.android.kt`:
|
||||
|
||||
```kotlin
|
||||
actual class DatabaseDriverFactory(private val context: Context) {
|
||||
actual fun createDriver(): SqlDriver {
|
||||
return AndroidSqliteDriver(
|
||||
schema = AppDatabase.Schema,
|
||||
context = context,
|
||||
name = "app.db"
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### iOS —
|
||||
|
||||
`shared/src/iosMain/kotlin/database/DatabaseDriverFactory.ios.kt`:
|
||||
|
||||
```kotlin
|
||||
actual class DatabaseDriverFactory {
|
||||
actual fun createDriver(): SqlDriver {
|
||||
return NativeSqliteDriver(
|
||||
schema = AppDatabase.Schema,
|
||||
name = "app.db"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Desktop —
|
||||
|
||||
`shared/src/desktopMain/kotlin/database/DatabaseDriverFactory.desktop.kt`:
|
||||
|
||||
```kotlin
|
||||
actual class DatabaseDriverFactory {
|
||||
actual fun createDriver(): SqlDriver {
|
||||
val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
|
||||
AppDatabase.Schema.create(driver)
|
||||
return driver
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Schritt 5: Repository erstellen
|
||||
|
||||
In `shared/src/commonMain/kotlin/repository/UserRepository.kt`:
|
||||
|
||||
```kotlin
|
||||
class UserRepository(database: AppDatabase) {
|
||||
|
||||
private val queries = database.userQueries
|
||||
|
||||
suspend fun insertUser(name: String, imageUrl: String?) = withContext(Dispatchers.IO) {
|
||||
queries.insertUser(name, imageUrl)
|
||||
}
|
||||
|
||||
suspend fun getAllUsers(): List<User> = withContext(Dispatchers.IO) {
|
||||
queries.getAllUsers().executeAsList()
|
||||
}
|
||||
|
||||
suspend fun getUserById(id: Long): User? = withContext(Dispatchers.IO) {
|
||||
queries.getUserById(id).executeAsOneOrNull()
|
||||
}
|
||||
|
||||
suspend fun updateUser(id: Long, name: String, imageUrl: String?) = withContext(Dispatchers.IO) {
|
||||
queries.updateUser(name, imageUrl, id)
|
||||
}
|
||||
|
||||
suspend fun deleteUser(id: Long) = withContext(Dispatchers.IO) {
|
||||
queries.deleteUser(id)
|
||||
}
|
||||
|
||||
suspend fun deleteAllUsers() = withContext(Dispatchers.IO) {
|
||||
queries.deleteAllUsers()
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Schritt 6: Koin-Module konfigurieren
|
||||
|
||||
In `shared/src/commonMain/kotlin/di/DatabaseModule.kt`:
|
||||
|
||||
```kotlin
|
||||
val databaseModule = module {
|
||||
single { DatabaseDriverFactory() }
|
||||
single { AppDatabase(get<DatabaseDriverFactory>().createDriver()) }
|
||||
single { UserRepository(get()) }
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Plattformspezifische Module
|
||||
|
||||
### Android —
|
||||
|
||||
`shared/src/androidMain/kotlin/di/PlatformModule.android.kt`:
|
||||
|
||||
```kotlin
|
||||
actual val platformModule = module {
|
||||
single { DatabaseDriverFactory(androidContext()) }
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### iOS —
|
||||
|
||||
`shared/src/iosMain/kotlin/di/PlatformModule.ios.kt`:
|
||||
|
||||
```kotlin
|
||||
actual val platformModule = module {
|
||||
single { DatabaseDriverFactory() }
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Desktop —
|
||||
|
||||
`shared/src/desktopMain/kotlin/di/PlatformModule.desktop.kt`:
|
||||
|
||||
```kotlin
|
||||
actual val platformModule = module {
|
||||
single { DatabaseDriverFactory() }
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Gemeinsame Modul-Deklaration —
|
||||
|
||||
`shared/src/commonMain/kotlin/di/PlatformModule.kt`:
|
||||
|
||||
```kotlin
|
||||
expect val platformModule: Module
|
||||
|
||||
```
|
||||
|
||||
## Schritt 7: Koin initialisieren
|
||||
|
||||
In `shared/src/commonMain/kotlin/di/KoinInit.kt`:
|
||||
|
||||
```kotlin
|
||||
fun initKoin(appDeclaration: KoinAppDeclaration = {}) = startKoin {
|
||||
appDeclaration()
|
||||
modules(
|
||||
platformModule,
|
||||
databaseModule
|
||||
)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Schritt 8: Plattform-Initialisierung
|
||||
|
||||
### Android —
|
||||
|
||||
In `MainActivity.kt`:
|
||||
|
||||
```kotlin
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
initKoin {
|
||||
androidContext(this@MainActivity)
|
||||
}
|
||||
|
||||
setContent {
|
||||
App()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### iOS —
|
||||
|
||||
In `iosApp/iosApp/iOSApp.swift`:
|
||||
|
||||
```swift
|
||||
@main
|
||||
struct iOSApp : App {
|
||||
|
||||
init() {
|
||||
KoinInitKt.doInitKoin()
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Desktop —
|
||||
|
||||
In `desktopApp/src/jvmMain/kotlin/main.kt`:
|
||||
|
||||
```kotlin
|
||||
fun main() {
|
||||
initKoin()
|
||||
|
||||
application {
|
||||
Window(onCloseRequest = ::exitApplication) {
|
||||
App()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Schritt 9: In Compose verwenden
|
||||
|
||||
### ViewModel erstellen —
|
||||
|
||||
In `shared/src/commonMain/kotlin/viewmodel/UserViewModel.kt`:
|
||||
|
||||
```kotlin
|
||||
class UserViewModel(private val userRepository: UserRepository) : ViewModel() {
|
||||
|
||||
var users by mutableStateOf<List<User>>(emptyList())
|
||||
private set
|
||||
|
||||
var isLoading by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
init {
|
||||
loadUsers()
|
||||
}
|
||||
|
||||
fun loadUsers() {
|
||||
viewModelScope.launch {
|
||||
isLoading = true
|
||||
users = userRepository.getAllUsers()
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
fun addUser(name: String, imageUrl: String?) {
|
||||
viewModelScope.launch {
|
||||
userRepository.insertUser(name, imageUrl)
|
||||
loadUsers()
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteUser(id: Long) {
|
||||
viewModelScope.launch {
|
||||
userRepository.deleteUser(id)
|
||||
loadUsers()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Im Compose-Screen verwenden:
|
||||
|
||||
```kotlin
|
||||
@Composable
|
||||
fun UserScreen() {
|
||||
val userViewModel: UserViewModel = koinInject()
|
||||
|
||||
LazyColumn {
|
||||
items(userViewModel.users) { user ->
|
||||
UserItem(
|
||||
user = user,
|
||||
onDelete = { userViewModel.deleteUser(user.id) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UserItem(user: User, onDelete: () -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = user.name,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
Button(onClick = onDelete) {
|
||||
Text("Löschen")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Fertig!
|
||||
|
||||
SQLDelight ist nun vollständig in das Compose Multiplatform-Projekt integriert mit:
|
||||
|
||||
- Datenbankbetrieb auf Android, iOS und Desktop
|
||||
- Koin Dependency Injection konfiguriert
|
||||
- Repository-Pattern für Clean Architecture
|
||||
- Einsatzbereite User-Tabelle mit CRUD-Operationen
|
||||
|
||||
Die Datenbank verwaltet automatisch die plattformspezifischen Implementierungen, während dieselbe Geschäftslogik auf allen Plattformen geteilt wird.
|
||||
@@ -0,0 +1,165 @@
|
||||
---
|
||||
type: Guide
|
||||
status: ACTIVE
|
||||
owner: Frontend Expert
|
||||
last_update: 2026-03-15
|
||||
---
|
||||
# Architekturstrategien für Asynchrone Persistenz in Kotlin Multiplatform: Eine umfassende Analyse zur Integration von SQLDelight in Web-Umgebungen
|
||||
|
||||
## 1. Einleitung und Problemstellung
|
||||
|
||||
Die Entwicklung plattformübergreifender Anwendungen mittels Kotlin Multiplatform (KMP) hat in den letzten Jahren einen paradigmatischen Wandel vollzogen. Ein zentraler Bestandteil dieser Architektur ist die Datenpersistenz, für die sich SQLDelight als Industriestandard etabliert hat.
|
||||
|
||||
Die Integration der Web-Plattform stellt jedoch eine signifikante architektonische Herausforderung dar. Wie in der Problemstellung korrekt identifiziert, existiert eine fundamentale Diskrepanz zwischen den synchronen I/O-Operationen nativer Plattformen (Android, iOS) und der zwingend asynchronen Natur des Webs. Während native SQLite-Treiber (`AndroidSqliteDriver`, `NativeSqliteDriver`) Datenbankoperationen blockierend ausführen können, erfordert der Browser die Nutzung eines `WebWorkerDriver` und asynchrone Initialisierungsmuster.
|
||||
|
||||
Dieser Bericht liefert eine Lösungsarchitektur basierend auf dem "Lazy Async Wrapper"-Muster und Koin.
|
||||
|
||||
---
|
||||
|
||||
## 2. Theoretisches Fundament: Die Asynchronitäts-Lücke
|
||||
|
||||
### 2.1 Native vs. Web-Laufzeitumgebungen
|
||||
|
||||
Auf nativen Systemen kann der `SqlDriver` synchron instanziiert werden. Im Browser hingegen nutzt SQLDelight `sql.js` oder `sqlite-wasm` in einem Web Worker. Die Kommunikation erfolgt über Message Passing, was `suspend`-Funktionen für die Initialisierung erzwingt.
|
||||
|
||||
### 2.2 Der Paradigmenwechsel mit SQLDelight 2.0
|
||||
|
||||
Mit Version 2.0 wurde die Konfiguration `generateAsync` eingeführt:kotlin sqldelight { databases { create("AppDatabase") { packageName.set("com.example.db") generateAsync.set(true) } } }
|
||||
|
||||
Setzt man dieses Flag auf `true`, werden alle Datenbankoperationen als `suspend`-Funktionen generiert.[1, 4] Dies ist der erste Schritt zur Vereinheitlichung: Auch native Plattformen nutzen nun (formal) asynchrone Schnittstellen, was den gemeinsamen Code homogenisiert.
|
||||
|
||||
---
|
||||
|
||||
## 3. Die Lösungsarchitektur: Das "Lazy Async Wrapper"-Muster
|
||||
|
||||
Anstatt die Datenbank direkt beim App-Start zu initialisieren (was im Web blockieren oder fehlschlagen würde, wenn der Worker noch nicht bereit ist), kapseln wir den Treiber in einer Wrapper-Klasse.[5, 2]
|
||||
|
||||
### 3.1 Definition der Factory
|
||||
|
||||
**Datei:** `shared/src/commonMain/kotlin/.../DatabaseDriverFactory.kt`
|
||||
|
||||
```kotlin
|
||||
interface DatabaseDriverFactory {
|
||||
suspend fun createDriver(): SqlDriver
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 3.2 Der Database Wrapper
|
||||
|
||||
Diese Komponente löst das Problem des Nutzers, indem sie die Initialisierung bis zum ersten Zugriff verzögert und mittels `Mutex` absichert.
|
||||
|
||||
**Datei:** `shared/src/commonMain/kotlin/.../DatabaseWrapper.kt`
|
||||
|
||||
```kotlin
|
||||
class DatabaseWrapper(private val driverFactory: DatabaseDriverFactory) {
|
||||
private var _database: AppDatabase? = null
|
||||
private val mutex = Mutex()
|
||||
|
||||
suspend fun get(): AppDatabase {
|
||||
_database?.let { return it }
|
||||
return mutex.withLock {
|
||||
_database?: AppDatabase(driverFactory.createDriver()).also { _database = it }
|
||||
}
|
||||
}
|
||||
|
||||
// Helper für Repositories
|
||||
suspend operator fun <R> invoke(block: suspend (AppDatabase) -> R): R {
|
||||
return block(get())
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Implementierung der Plattform-Treiber
|
||||
|
||||
### 4.1 Web (Kotlin/Wasm & JS)
|
||||
|
||||
Hier liegt der Kern der Lösung: Wir warten explizit auf die Schema-Erstellung (`awaitCreate`), bevor wir den Treiber zurückgeben.
|
||||
|
||||
**Datei:** `shared/src/jsMain/kotlin/.../WebDatabaseDriverFactory.kt`
|
||||
|
||||
```kotlin
|
||||
class WebDatabaseDriverFactory : DatabaseDriverFactory {
|
||||
override suspend fun createDriver(): SqlDriver {
|
||||
val worker = Worker(
|
||||
js("""new URL("@cashapp/sqldelight-sqljs-worker/sqljs.worker.js", import.meta.url)""")
|
||||
)
|
||||
val driver = WebWorkerDriver(worker)
|
||||
|
||||
// WICHTIG: Hier wird asynchron gewartet!
|
||||
AppDatabase.Schema.create(driver).await()
|
||||
return driver
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
**Webpack Konfiguration:**
|
||||
Damit dies funktioniert, muss die `sql-wasm.wasm` Datei korrekt kopiert werden.
|
||||
|
||||
```javascript
|
||||
// webpack.config.d/sqljs.js
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
config.plugins.push(
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
'../../node_modules/sql.js/dist/sql-wasm.wasm'
|
||||
]
|
||||
})
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
### 4.2 Android (Synchron)
|
||||
|
||||
Für Android geben wir den synchronen Treiber einfach in der `suspend`-Funktion zurück.
|
||||
|
||||
```kotlin
|
||||
class AndroidDatabaseDriverFactory(private val context: Context) : DatabaseDriverFactory {
|
||||
override suspend fun createDriver(): SqlDriver {
|
||||
return AndroidSqliteDriver(AppDatabase.Schema, context, "app.db")
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Integration mit Koin
|
||||
|
||||
Da der `DatabaseWrapper` selbst leichtgewichtig ist (er erstellt die DB noch nicht im Konstruktor), kann er problemlos als `single` in Koin registriert werden.
|
||||
|
||||
```kotlin
|
||||
val appModule = module {
|
||||
single { DatabaseWrapper(get()) }
|
||||
single { MyRepository(get()) }
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Das Repository nutzt dann den Wrapper:
|
||||
|
||||
```kotlin
|
||||
class MyRepository(private val dbWrapper: DatabaseWrapper) {
|
||||
suspend fun getItems() = dbWrapper { db ->
|
||||
db.itemQueries.selectAll().executeAsList()
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 6. Zusammenfassung
|
||||
|
||||
Diese Architektur löst den Konflikt zwischen synchronen und asynchronen Welten durch:
|
||||
|
||||
1. **`generateAsync = true`**: Erzwingt `suspend` überall.
|
||||
|
||||
|
||||
2. **Wrapper Pattern**: Kapselt die asynchrone Initialisierung (`await()`) im Web.
|
||||
|
||||
|
||||
3. **Koin Singleton**: Der Wrapper kann sofort injiziert werden, die DB wird erst beim ersten `invoke` geladen.
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
type: Guide
|
||||
status: ACTIVE
|
||||
owner: DevOps Engineer
|
||||
tags: [git, workflow, pr, branching]
|
||||
---
|
||||
|
||||
# Branchschutz & Pull-Request Workflow
|
||||
|
||||
Diese Anleitung beschreibt einen einfachen, robusten Flow für `main` mit kurzen Feature-Branches und klaren
|
||||
Qualitätschecks.
|
||||
|
||||
## 1) Branch-Naming
|
||||
|
||||
- Feature: `feature/<kurz-beschreibung>`
|
||||
- Bugfix: `fix/<kurz-beschreibung>`
|
||||
- Docs: `docs/<kurz-beschreibung>`
|
||||
|
||||
Optional: Issue-Key voranstellen, z. B. `feature/MP-7-doku-konsolidieren`.
|
||||
|
||||
## 2) Pull Request (PR)
|
||||
|
||||
- PR-Titel nach Conventional Commits (kurz): `docs(api): Front‑Matter vereinheitlicht (MP-7)`
|
||||
- Beschreibung kurz mit Bulletpoints; DoD-Checkliste abhaken (Template vorhanden)
|
||||
- CI muss grün sein (Backend + Docs)
|
||||
|
||||
## 3) Branchschutz (GitHub Einstellungen → Branches → main)
|
||||
|
||||
- Require a pull request before merging
|
||||
- Require status checks to pass before merging
|
||||
- aktivieren: `CI Docs`, `CI` (Backend falls vorhanden)
|
||||
- Require linear history
|
||||
- Require approvals: mindestens 1 (bei Solo-Projekt optional, aber empfohlen)
|
||||
- Allow squash merging only
|
||||
- Disallow force pushes, Disallow deletions
|
||||
|
||||
## 4) Commits & YouTrack
|
||||
|
||||
- Commit-Message enthält Issue-Key (z. B. `MP-7`) → erleichtert Nachverfolgung
|
||||
- In Doku-Front‑Matter `yt_epic`/`yt_issues` pflegen
|
||||
- Optional: GitHub Secrets `YT_URL`, `YT_TOKEN` setzen → CI validiert verlinkte Issues, und `youtrack-sync.yml`
|
||||
kommentiert beim Merge automatisch ins Issue
|
||||
|
||||
## 5) Definition of Done (Auszug)
|
||||
|
||||
- Doku aktuell (README/ADR/C4/API)
|
||||
- Front‑Matter valide (`modul`, `status`, `summary`, optional `last_reviewed`, `review_cycle`, `yt_*`)
|
||||
- Links funktionieren (CI link-check grün)
|
||||
- Tests grün
|
||||
|
||||
## 6) Lokale Tipps
|
||||
|
||||
- Vor dem Push: `markdownlint`, `vale` lokal laufen lassen (optional via pre-commit hooks)
|
||||
- kleine, häufige PRs statt großer Monster-PRs
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
type: Guide
|
||||
status: ACTIVE
|
||||
owner: DevOps Engineer
|
||||
tags: [setup, local, docker, gradle]
|
||||
---
|
||||
|
||||
# Start Local (Lokales Setup)
|
||||
|
||||
Kurzanleitung, um das Projekt lokal in wenigen Minuten zu starten – Desktop‑First mit optionalem Docker‑Backend.
|
||||
|
||||
## Voraussetzungen (exakte Versionen)
|
||||
- Git ≥ 2.40
|
||||
- JDK 25 (Temurin/Eclipse Adoptium empfohlen) – Projekttoolchain lädt bei Bedarf automatisch
|
||||
- Gradle Wrapper 9.4.0 (wird über `./gradlew` automatisch verwendet)
|
||||
- Docker Engine ≥ 24, Docker Compose v2 ≥ 2.24
|
||||
|
||||
Hinweise:
|
||||
- Die Java‑Toolchain wird per Gradle automatisch heruntergeladen (`org.gradle.java.installations.auto-download=true`). Ein lokal installiertes JDK 25 wird dennoch empfohlen für IDE‑Runs.
|
||||
- Auf Apple‑Silicon (arm64) sind die Docker‑Images optimiert; Keycloak nutzt `start --optimized`.
|
||||
|
||||
## Schnellstart (nur Backend in Docker)
|
||||
|
||||
```bash
|
||||
# 1) Repository klonen
|
||||
git clone https://github.com/StefanMoCoAt/meldestelle.git
|
||||
cd meldestelle
|
||||
|
||||
# 2) Runtime-Environment vorbereiten
|
||||
# Kopiere die Vorlage.
|
||||
cp .env.example .env
|
||||
|
||||
# 3) Infrastruktur starten (Postgres, Valkey, Keycloak, Tracing, Service Discovery)
|
||||
docker compose --profile infra up -d
|
||||
|
||||
# 4) Backend starten (Gateway + Ping Service)
|
||||
docker compose --profile backend up -d
|
||||
```
|
||||
|
||||
Sobald die Infrastruktur läuft, erreichst du unter anderem:
|
||||
- Gateway (API): http://localhost:8081
|
||||
- Keycloak (IAM): http://localhost:8180
|
||||
- Zipkin (Tracing): http://localhost:9411
|
||||
- Consul (Service Discovery): http://localhost:8500
|
||||
- Optional Web‑App (falls `--profile gui` gebaut/gestartet): http://localhost:4000
|
||||
|
||||
## Desktop‑App starten (Compose Desktop)
|
||||
|
||||
Die Desktop‑App ist der primäre Entwicklungs‑Entry‑Point.
|
||||
|
||||
```bash
|
||||
# 1) Abhängigkeiten bauen (optional; Gradle lädt automatisch beim ersten Run)
|
||||
./gradlew :frontend:shells:meldestelle-desktop:build
|
||||
|
||||
# 2) Desktop‑App starten
|
||||
./gradlew :frontend:shells:meldestelle-desktop:run
|
||||
```
|
||||
|
||||
Voraussetzung: Für Features, die Backend‑Konnektivität benötigen (z. B. Login, Stammdaten), muss das Docker‑Backend (infra + backend) laufen. Für rein lokale/offline Flows kann die App auch ohne Docker gestartet werden.
|
||||
|
||||
## Tests ausführen
|
||||
```bash
|
||||
# Führt alle Tests aus
|
||||
./gradlew test
|
||||
|
||||
# Spezifisches Backend-Modul testen
|
||||
./gradlew :backend:services:ping:ping-service:test
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
- Dienste starten nicht? Ports belegt oder Logs prüfen:
|
||||
```bash
|
||||
docker ps
|
||||
docker logs <container-name>
|
||||
```
|
||||
- Infrastruktur neu starten:
|
||||
```bash
|
||||
docker compose down -v
|
||||
docker compose --profile infra up -d
|
||||
```
|
||||
- Environment‑Variablen: werden aus der `.env`‑Datei im Root‑Verzeichnis geladen.
|
||||
- Gradle/Java Probleme? Stelle sicher, dass JDK 25 aktiv ist bzw. lasse die Gradle‑Toolchain das passende JDK laden.
|
||||
- ARM64 (Apple Silicon): Falls Images nicht starten, lösche alte Images und starte neu: `docker compose down -v && docker system prune -af && docker compose --profile infra up -d`.
|
||||
|
||||
## Weiterführende Hinweise
|
||||
- README Quick‑Start (Desktop‑First): `README.md`
|
||||
- Architektur‑Kurzkonzept (Offline‑First Desktop & Backend): `docs/01_Architecture/konzept-offline-first-desktop-backend-de.md`
|
||||
- ADRs: `docs/01_Architecture/adr/`
|
||||
- Aktuelle Reports: `docs/90_Reports/`
|
||||
Reference in New Issue
Block a user