chore(MP-30): final docs cleanup, adr consolidation & legacy removal

Summary
- **Documentation Cleanup:**
  - ADRs consolidated to `docs/adr/`.
  - C4 diagrams moved to `docs/c4/`.
  - Removed legacy folder `docs/architecture/` and `docs/clients/`.
- **New ADR:** Added `0009-final-kmp-architecture.md` (Accepted).
- **Updates:**
  - `ARCHITECTURE.md` updated with final folder structure (Core Modules).
  - `README.md` links fixed.
- **Verification:**
  - `./gradlew staticAnalysis` -> SUCCESS.
  - Path references checked.

Ref: MP-30
This commit is contained in:
2025-12-08 18:04:44 +01:00
parent 5ea4730cd4
commit 14770003bd
31 changed files with 61 additions and 761 deletions
+9
View File
@@ -12,8 +12,15 @@ frontend/ KMP Frontend
shells Ausführbare Apps (Assembler)
features Vertical Slices (kein Feature→Feature)
core Shared Foundation (Design-System, Network, Local-DB, Auth, Domain)
design-system
domain
network
local-db
navigation
docker/ Docker Compose, .env.example, Monitoring-/Core-Konfiguration
docs/ Architektur, ADRs, C4-Modelle, Guides
adr Architecture Decision Records (ADRs)
c4 C4-Diagramme (PlantUML Quellen)
Ist → Soll Mapping (erste Tranche)
@@ -65,3 +72,5 @@ Nächste Schritte (MP-22 Folgetasks)
2. Physisches Verschieben der Backend-Komponenten in backend/* inkl. evtl. Package-Pfade, sofern notwendig.
3. Ergänzung von docker-compose.services.yml und docker-compose.clients.yml mit echten Overlays.
4. Erstellen der ersten ADRs unter docs/adr (Koin, SQLDelight, Optimistic Locking, Freshness UI, Core Domain).
Hinweis: ADRs liegen ab sofort zentral unter `docs/adr/` (nicht mehr unter `docs/architecture/adr/`). C4-Diagramme wurden nach `docs/c4/` verschoben.
+14 -13
View File
@@ -23,23 +23,24 @@ Die Hauptdokumentation befindet sich in der **YouTrack Wissensdatenbank**:
Architekturentscheidungen sind Teil der Code-Historie und werden im Repository versioniert:
- [ADR Übersicht](architecture/adr)
- [ADR-0001: Modulare Architektur](architecture/adr/0001-modular-architecture-de.md)
- [ADR-0002: Domain-Driven Design](architecture/adr/0002-domain-driven-design-de.md)
- [ADR-0003: Microservices](architecture/adr/0003-microservices-architecture-de.md)
- [ADR-0004: Event-Driven Communication](architecture/adr/0004-event-driven-communication-de.md)
- [ADR-0005: Polyglot Persistence](architecture/adr/0005-polyglot-persistence-de.md)
- [ADR-0006: Authentication & Authorization (Keycloak)](architecture/adr/0006-authentication-authorization-keycloak-de.md)
- [ADR-0007: API Gateway Pattern](architecture/adr/0007-api-gateway-pattern-de.md)
- [ADR-0008: Multiplatform Client Applications](architecture/adr/0008-multiplatform-client-applications-de.md)
- [ADR Übersicht](adr)
- [ADR-0001: Modulare Architektur](adr/0001-modular-architecture-de.md)
- [ADR-0002: Domain-Driven Design](adr/0002-domain-driven-design-de.md)
- [ADR-0003: Microservices](adr/0003-microservices-architecture-de.md)
- [ADR-0004: Event-Driven Communication](adr/0004-event-driven-communication-de.md)
- [ADR-0005: Polyglot Persistence](adr/0005-polyglot-persistence-de.md)
- [ADR-0006: Authentication & Authorization (Keycloak)](adr/0006-authentication-authorization-keycloak-de.md)
- [ADR-0007: API Gateway Pattern](adr/0007-api-gateway-pattern-de.md)
- [ADR-0008: Multiplatform Client Applications](adr/0008-multiplatform-client-applications-de.md)
- [ADR-0009: Final KMP Architecture](adr/0009-final-kmp-architecture.md)
### 2. C4-Diagramme (PlantUML-Quellen)
Versionierte Diagramm-Quellen für Architekturdokumentation:
- [C4 Context](architecture/c4/01-context-de.puml)
- [C4 Container](architecture/c4/02-container-de.puml)
- [C4 Component - Events Service](architecture/c4/03-component-events-service-de.puml)
- [C4 Context](c4/01-context-de.puml)
- [C4 Container](c4/02-container-de.puml)
- [C4 Component - Events Service](c4/03-component-events-service-de.puml)
### 3. Developer Guides
@@ -72,7 +73,7 @@ Das Projekt nutzt automatisierte Workflows für Konsistenz:
### Für Architektur-Entscheidungen
1. ADR in `docs/architecture/adr/` erstellen
1. ADR in `docs/adr/` erstellen
2. PR mit ADR-Review
3. Nach Merge → Zusammenfassung in YouTrack verlinken
+35
View File
@@ -0,0 +1,35 @@
# ADR-0009: Final KMP Architecture
Status: Accepted
Kontext
Wir schließen die Architektur-Entscheidungen für das Frontend als Kotlin Multiplatform (KMP) Projekt ab und bestätigen die finalen Bausteine sowie die Modulaufteilung. Die Plattformen Web (JS/WASM) und JVM/Desktop werden unterstützt.
Entscheidung
1. Plattformen: Kotlin Multiplatform mit Targets Web (JS/WASM) und JVM/Desktop.
2. Dependency Injection: Koin als DI-Framework für gemeinsame und plattformspezifische Layer.
3. Persistenz (Offline-First): SQLDelight als lokale Datenbank (Single Source of Truth), synchronisiert im Hintergrund.
4. Modulaufteilung im Frontend:
- shells: Ausführbare App-Shells (Bootstrap/Assembler, DI-Start, Plattformintegration)
- features: Vertikale Slices mit UI, Domain-Logik und Navigation pro Feature
- core: Gemeinsame Basis (design-system, domain, network, local-db, navigation)
5. Kommunikation: Features reden nicht direkt miteinander; Navigation + Shared-Domain-Modelle in core/domain.
Begründung
- KMP erlaubt maximale Codewiederverwendung über Web und JVM bei konsistentem Tooling.
- Koin bietet leichtgewichtige, idiomatische DI ohne Code-Generierung, geeignet für KMP.
- SQLDelight liefert typsichere Queries, portable Schemas und ist für Offline-First praxiserprobt.
- Die Trennung in shells/features/core fördert klare Zuständigkeiten, Testbarkeit und schrittweise Erweiterbarkeit.
Konsequenzen
- Projektstruktur und Gradle-Module folgen strikt der Aufteilung in `shells`, `features`, `core`.
- Der `apiClient` (core/network) wird via Koin als Named Binding injiziert; manuelles Setzen von Authorization-Headern ist untersagt.
- UI liest aus der lokalen Datenbank (SQLDelight); Synchronisation erfolgt über Hintergrundjobs.
Status / Nacharbeiten
- Diese ADR konsolidiert die KMP-Entscheidung und ersetzt frühere verstreute Notizen. Weitere Details (z. B. konkrete Module, Pfade) sind in `docs/ARCHITECTURE.md` dokumentiert.
-86
View File
@@ -1,86 +0,0 @@
# Meldestelle Clients Architekturübersicht (aktualisiert)
## Ziele
- Öffentliche Willkommensseite mit Links zu Ping-Service, Keycloak Login/Registrierung
- Einfache Auth-Status-Seite („Du bist als … angemeldet“)
- Bereinigung der Legacy-UI (altes Präsentations-Layer entfernt)
- Bereitstellung als Docker-Container: `web-app` (Kotlin/JS) und `desktop-app` (VNC/noVNC)
## Module und Struktur
- `clients/app`: Einstieg der Anwendung (Compose Multiplatform)
- `MainApp.kt`: Start-Routing, Composables `WelcomeScreen`, `LoginScreen`, `AuthStatusScreen`
- Verwendet: `ping-feature` (PingScreen), `auth-feature` (Login via `AuthApiClient`)
- `clients/shared`: Gemeinsame Domain/Data/DI + `AppConfig`
- `AppConfig`: Basis-URLs und Keycloak-Client-Konfiguration
- `clients/shared/common-ui`: generische UI-Bausteine (Legacy-Teile neutralisiert)
- `layout/MainLayout.kt`, `components/NotificationCard.kt`, `screens/DashboardScreen.kt` → bewusst geleert
- `clients/auth-feature`: Login-API gegen Keycloak (Password Grant)
- `clients/ping-feature`: Ping-Screen und ViewModel, greift auf Ping-Service zu
## Navigation (vereinfacht)
- Start: `AppScreen.Home``WelcomeScreen`
- `AppScreen.Ping``PingScreen`
- `AppScreen.Login``LoginScreen`
- `AppScreen.Profile``AuthStatusScreen`
## Keycloak-Konfiguration
Quelle: `docker/core/keycloak/meldestelle-realm.json` und `docs/reference/ports-and-urls.md`
- Realm: `meldestelle`
- Öffentlicher Client: `web-app` (PKCE; für Browser)
- Keycloak URL (lokal): `http://localhost:8180`
- AppConfig nutzt: `KEYCLOAK_URL=http://localhost:8180`, `KEYCLOAK_REALM=meldestelle`, `KEYCLOAK_CLIENT_ID=web-app`
### Authentifizierungs-Flow (PKCE)
- Flow: Authorization Code Flow mit PKCE (S256)
- Redirect-URI (lokal): `http://localhost:4000/` (Root, Query-Parameter werden vom Client ausgewertet)
- Ablauf:
1. Button „Login“ → PKCE-Start (Code Verifier/Challenge werden generiert) und Redirect zu Keycloak `/auth`
2. Keycloak leitet zurück auf `http://localhost:4000/?code=...&state=...`
3. Client tauscht `code` + `code_verifier` am `/token`-Endpoint gegen Tokens
4. `AuthTokenManager` speichert Access-Token (in-memory), UI zeigt Status „Du bist als … angemeldet“
Hinweis: Password-Grant wird nicht mehr genutzt; für Desktop (JVM) bleibt bei Bedarf der lokale Login-Screen als Fallback vorhanden.
## Ports und URLs (lokal)
Quelle: `docs/reference/ports-and-urls.md`
- API Gateway: `http://localhost:8081`
- Keycloak: `http://localhost:8180`
- Ping Service: `http://localhost:8082`
- Web App: `http://localhost:4000`
- Desktop App: VNC `5901`, noVNC `http://localhost:6080`
## Docker
### Web-App (Kotlin/JS, kein WASM)
- Dockerfile: `dockerfiles/clients/web-app/Dockerfile`
- Build: Gradle `:clients:app:jsBrowserDistribution` → statische Dateien via Nginx
- Compose:
- Hardcoded: Service `web-app` mit Port `4000:4000` in `compose.hardcoded.yaml`
- Variablen: Service `web-app` mit `${WEB_APP_PORT}` in `compose.yaml` (Wert in `.env`)
Downloads (Desktop-Installer, Platzhalter):
- Verzeichnis: `dockerfiles/clients/web-app/downloads/` → wird nach `/usr/share/nginx/html/downloads/` kopiert
- URL: `http://localhost:4000/downloads/`
- Alternativ: per Compose ein Host-Verzeichnis auf `/usr/share/nginx/html/downloads` mounten
### Desktop-App (VNC/noVNC)
- Dockerfile: `dockerfiles/clients/desktop-app/Dockerfile`
- Build: Gradle `:clients:app:createDistributable` → Desktop Runtime
- Runtime: Ubuntu + `xvfb` + `x11vnc` + `noVNC` + `supervisord`
- Compose:
- Hardcoded: Service `desktop-app` 5901/6080 in `compose.hardcoded.yaml`
- Variablen: Service `desktop-app` mit `${DESKTOP_APP_VNC_PORT}` und `${DESKTOP_APP_NOVNC_PORT}` in `compose.yaml` (Werte in `.env`)
## Bereinigung (Altlasten)
- Entfernt/neutralisiert: Altes Präsentations-Layer (abhängig von `presentation.state`/`actions`)
- `clients/shared/common-ui/components/NotificationCard.kt` → geleert
- `clients/shared/common-ui/layout/MainLayout.kt` → geleert
- `clients/shared/common-ui/screens/DashboardScreen.kt` → geleert
## Konfigurationsquelle
- Einheitliche Werte/Ports: `docker/versions.toml` (Clients: `web-app=4000`, `desktop-app-vnc=5901`, `desktop-app-novnc=6080`)
- Compose-Variablen: `.env` und `.env.template`
## Nächste Schritte
- Optional: Umstellung Login auf Authorization Code Flow (PKCE) für Browser
- Optional: Willkommensseite visuell ausbauen (Branding)
- Optional: Bereitstellung der Desktop-Installer über `web-app` Download-Link
@@ -1,161 +0,0 @@
### 1\. Welche DI-Lösung? (Dependency Injection)
**Entscheidung:** Wir nutzen **Koin**.
**Begründung (ADR):**
* **Warum nicht Dagger/Hilt?** Hilt ist stark auf Android (Context, Lifecycles) fixiert. Dagger ist extrem komplex im Setup für Multiplatform (Kapt/KSP Setup über alle Targets).
* **Warum Koin?** Es ist ein reines Kotlin-Framework ("Service Locator" Pattern). Es funktioniert identisch auf JVM (Desktop), JS (Web) und Android. Es benötigt keine Annotation-Processing-Magie, was die Build-Zeiten im Monorepo niedrig hält.
**Eintrag im Guide:**
```text
// GUIDELINE: Dependency Injection
// Wir nutzen Koin. Module werden im `di` Package des Features definiert.
// 1. Definition (Feature Module)
val inventoryModule = module {
// Singletons für Services
single<InventoryRepository> { InventoryRepositoryImpl(get(), get()) }
// ViewModels (Factory scope)
viewModel { InventoryViewModel(get()) }
}
// 2. Nutzung des ApiClients (Best Practice)
// Wir injizieren IMMER den "apiClient" (mit Auth-Header), niemals den Default Client.
val networkModule = module {
// Hinweis: Platzhalter (kein ausführbarer Code im Dokument)
single(named("apiClient")) { /* bereitgestellt in :core:network */ }
}
val myFeatureModule = module {
single {
// Explizites Holen des authentifizierten Clients
MyFeatureApi(httpClient = get(named("apiClient")))
}
}
```
-----
### 2\. Welche Offline-DB/ORM?
**Entscheidung:** Wir nutzen **SQLDelight**.
**Begründung (ADR):**
* **Warum nicht Room (KMP)?** Room ist für KMP noch sehr neu (Alpha/Beta Status) und bringt viel Overhead mit sich (SQLite Bundling etc.).
* **Warum SQLDelight?**
1. **Schema First:** Du schreibst SQL (`.sq`), und Kotlin-Code wird *generiert*. Das zwingt Entwickler dazu, über ihr Datenmodell nachzudenken, bevor sie Code schreiben.
2. **Performance:** Es ist extrem leichtgewichtig und typ-sicher.
3. **Migrationen:** SQLDelight hat ein exzellentes System für Schema-Migrationen (`1.sqm`, `2.sqm`), was für Desktop-Apps (die nicht einfach "neu geladen" werden können wie Webseiten) essenziell ist.
**Eintrag im Guide:**
> **DB-Guideline:**
>
> * Jedes Feature definiert sein Schema in `:frontend:core:local-db/src/commonMain/sqldelight/...`.
> * Business-Logik darf niemals SQL-Strings enthalten. Nutze die generierten `Queries`-Objekte.
> * Migrationen sind Pflicht bei Schema-Änderungen\! (Kein `DROP TABLE` in Production).
-----
### 3\. Konfliktstrategie bei Sync?
**Entscheidung:** **Optimistic Locking** (Server Wins).
**Begründung (ADR):**
* In einem System mit Offline-Clients ist "Last Write Wins" gefährlich (Lagerbestand wird überschrieben).
* **Strategie:**
1. Jedes Entity hat eine `lastUpdated` (Timestamp) Spalte.
2. Der Client sendet beim Update die Version mit, die er *kennt*.
3. Wenn Server-Version \> Client-Version → **HTTP 409 Conflict**.
4. Client muss Daten neu laden (Refresh) und User fragen/informieren.
**Eintrag im Guide:**
```kotlin
// GUIDELINE: Sync & Conflicts
// Das Frontend führt KEIN komplexes Merging durch.
suspend fun updateStock(item: Item) {
try {
api.update(item.id, item.newStock, currentVersion = item.version)
// Happy Path: DB Update
} catch (e: ConflictException) { // HTTP 409
// 1. Markiere Item in UI als "Out of Sync" (Rot)
// 2. Trigger automatischen Refresh vom Server
// 3. Zeige User Toast: "Daten waren veraltet. Bitte prüfen."
repo.refreshSingleItem(item.id)
}
}
```
-----
### 4\. Error Budgets / SLIs (Stale Data Indikatoren)
**Entscheidung:** **Visual Freshness Indicators** (Ampel-System).
**Begründung (ADR):**
* Ein User muss wissen, ob der Lagerbestand "live" ist oder "von gestern".
* Wir definieren keine harten Timeouts (App blockieren), sondern weiche UI-Hinweise.
**Eintrag im Guide:**
> **UI-Regel "Data Freshness":**
> Jedes Entity in der lokalen DB hat ein Feld `lastSyncedAt`. Das UI reagiert darauf:
>
> * **\< 5 min:** ✅ Normalzustand (Kein Indikator).
> * **\> 5 min:** ⚠️ Kleines gelbes "Wolke"-Icon oder ausgegrauter Text (Warnung).
> * **\> 1 Stunde:** ❌ Roter Banner "Offline-Daten: Bestand nicht garantiert".
> * **Aktion:** Schreibende Operationen sind bei "Rot" für kritische Bereiche (z.B. Inventur-Abschluss) gesperrt, für unkritische (z.B. Notiz anlegen) erlaubt (Queue).
-----
### 5\. API-Verträge und Kapselung der Feature-Teams
**Entscheidung:** **Loose Coupling via Navigation Routes & Shared Data Models (Core)**.
**Begründung (ADR):**
* Wir wollen vermeiden, dass Team A (Inventory) direkt Klassen von Team B (Checkout) importiert. Das führt zum "Monolithen-Klumpen".
* Wir nutzen **keine** separaten Gradle-Module pro Feature-API (`:inventory-api`, `:inventory-impl`), da dies den Build-Graph unnötig aufbläht ("Gradle Overhead").
**Strategie:**
1. **Schnittstelle:** Die einzige "Public API" eines Features ist sein `EntryPoint` (Composable) und seine `Route` (String).
2. **Datenaustausch:**
* *Minimal:* Über URL-Parameter (IDs). `navigator.navigate("inventory/details/123")`.
* *Objekte:* Wenn komplexe Objekte geteilt werden müssen (z.B. `UserProfile`), gehören diese in **`:frontend:core:domain`** (Shared Kernel).
**Eintrag im Guide:**
```text
// GUIDELINE: Feature Isolation
// 1. Features importieren NIEMALS andere Features im `build.gradle.kts`.
// 2. Kommunikation nur über Navigation (Router).
// 3. Gemeinsam genutzte Datenobjekte (z.B. UserID, ShopID) liegen in :core:domain.
// FALSCH:
// import com.project.features.billing.Invoice // Abhängigkeit zu anderem Feature! (nur zu Illustrationszwecken)
// RICHTIG:
// Feature A navigiert zu Feature B via Route
navigator.navigateTo("billing/create?orderId=123")
```
-----
### Zusammenfassung für dein Dokument
Diese 5 Punkte schließen den Kreis:
1. **Koin** hält den Code sauber.
2. **SQLDelight** hält die Daten sicher.
3. **Optimistic Locking** verhindert Datenmüll.
4. **Freshness UI** managed die Erwartungshaltung des Users.
5. **Core Domain** verhindert Spaghetti-Code zwischen Features.
-188
View File
@@ -1,188 +0,0 @@
## Frontend-Architektur
# Frontend-Architektur
**Architektur-Stil:** Feature-First, Clean Architecture, Kotlin Multiplatform (KMP)
## 1. Management Summary
Das Frontend wird als **modulare Kotlin Multiplatform (KMP) Anwendung** entwickelt. Ziel ist eine strikte Trennung von
technischer Basis (`shared`) und fachlichen Funktionen (`features`). Die Architektur ist **"Offline-Ready"** konzipiert:
Durch die konsequente Nutzung des Repository-Patterns kann die Datenquelle später transparent von "Online-Only" (API)
auf "Local-First" (Datenbank + Sync) umgestellt werden, ohne die Benutzeroberfläche (UI) anpassen zu müssen.
## 2. Die Modul-Struktur (Gradle)
Die Anwendung ist nicht monolithisch, sondern in fachliche Module geschnitten ("Feature-First").
- **`:clients:app` (Der Container)**
- **Rolle:** Der Einstiegspunkt ("Main").
- **Aufgabe:**
Initialisierung von Koin (Dependency Injection).
Globales Theming (Material3).
High-Level Navigation (Routing zwischen Features).
Verbindet alle Feature-Module.
- **`:clients:shared` (Das Fundament)**
- **Rolle:** Die gemeinsame Bibliothek für alle Features.
- **Inhalt:**
**DI (Dependency Injection):** Zentrale Koin-Module (NetworkModule, CoreModule).
**Network:** Konfigurierter HttpClient (Singleton).
**Domain Core:** Basis-Modelle (Resource<T>, ApiError, User).
**UI Kit:** Gemeinsame Komponenten (LoadingSpinner, ErrorView), Typography, Colors.
- **Regel:** Hier liegt keine Fachlogik eines spezifischen Features (keine Pferdedaten, keine Nennungen).
- **`:clients:*-feature` (Die Fachlichkeit)**
- **Beispiele:** `:clients:auth-feature`, `:clients:registry-feature`, `:clients:events-feature`.
- **Rolle:** Kapselt einen kompletten fachlichen Bereich.
- **Inhalt:** Eigene Screens, ViewModels und spezifische Use-Cases.
## 3. Die Schichten-Architektur (Innerhalb eines Moduls)
Jedes Modul (besonders `shared` und die Features) folgt einer strikten 3-Schichten-Architektur. Das garantiert
Testbarkeit und Austauschbarkeit.
### 1️⃣ Presentation Layer (UI)
- **Technologie:** Compose Multiplatform.
- **Komponenten:** `Screen` (Composable Functions) und `ViewModel` (State-Holder).
- **Verantwortung:**
Zeigt Daten an (Reagiert auf State).
Nimmt User-Input entgegen.
**Weiß NICHT**, woher die Daten kommen (Netzwerk oder DB).
Nutzt Repositories via Koin Injection (`koinInject()`).
### 2️⃣ Domain Layer (Die Logik)
- **Komponenten:** `Repository Interface`, `Models`.
- **Verantwortung:**
Definiert **WAS** getan werden kann (z.B. `checkSystemStatus()`).
Ist komplett unabhängig von Frameworks (reines Kotlin).
Nutzt `Resource<T>` Wrapper (`Success`, `Error`, `Loading`) für den State-Transport.
### 3️⃣ Data Layer (Die Umsetzung)
- **Komponenten:** `Repository Implementation`, `API-DTOs`, `Database-Entities`.
- **Verantwortung:**
Entscheidet **WIE** Daten geholt werden.
**Aktuell (Online):** Ruft Ktor Client auf.
**Zukunft (Offline/LAN):** Prüft lokale SQLite DB, synchronisiert im Hintergrund.
Mappt rohe API-Daten (DTOs) in saubere Domain-Modelle.
### 4. Technische Kern-Konzepte
**💉 Dependency Injection (Koin)**
Wir nutzen Koin als "Klebstoff".
- Module exportieren ihre Funktionalität via `val myModule = module { single { ... } }`.
- Die `clients:app` sammelt alle Module ein und startet den Container.
- **Vorteil:** Repositories und der HTTP-Client müssen nicht manuell herumgereicht werden.
**🌐 Networking (Ktor)**
Ein zentraler `HttpClient` im `NetworkModule` (`clients:shared`).
- Konfiguriert mit JSON-Serialization.
- Zentrale Base-URL Steuerung (via `AppConfig` umschaltbar für LAN/Dev/Prod).
- Timeout- und Logging-Handling an einer Stelle.
**🔄 Datenfluss (Unidirectional Data Flow)**
1. **UI:** Trigger Event (Button Click).
2. **ViewModel/Scope:** Ruft Repository auf (`launch { repo.getData() }`).
3. **Repository:** Liefert `Resource.Loading` -> `Resource.Success(data)`.
4. **UI:** Rendert den neuen State.
### 5. Strategie für Offline & LAN-Fähigkeit (Zukunft)
Die aktuelle Architektur ist **vorbereitet** für die Offline-Anforderungen der großen Turniere.
**Die Transition (Schritt-für-Schritt):**
1. **Phase 1 (Jetzt):** PingRepositoryImpl ruft direkt httpClient.get() auf.
2. **Phase 2 (Offline):**
Wir fügen **SQLDelight** (lokale DB) hinzu.
Die `PingRepositoryImpl` wird geändert (ohne dass die UI es merkt!):
```Kotlin
fun getData() {
val localData = db.dao.getAll()
if (localData.isEmpty()) {
val remote = api.getAll()
db.dao.insert(remote)
return remote
}
return localData
}
```
3. **Phase 3 (Sync):** Ein Hintergrund-Worker (`WorkManager` oder `Coroutine`) synchronisiert lokale Änderungen mit dem
Server, sobald das LAN verfügbar ist.
### 6. Zusammenfassung für Entwickler
Wenn du ein neues Feature (z.B. "Pferde anzeigen") baust:
1. **Domain:** Erstelle Horse Model und HorseRepository Interface.
2. **Data:** Erstelle HorseRepositoryImpl. Injiziere den HttpClient.
3. **DI:** Registriere das Repository im Koin-Modul.
4. **UI:** Erstelle HorseListScreen. Injiziere das Repository.
5. Fertig.
Diese Struktur ist sauber, skalierbar und bereit für den professionellen Einsatz beim OEPS. ✅
-14
View File
@@ -1,14 +0,0 @@
## Epic 1: BigBang vorbereiten Snapshot und Branching
* Zusammenfassung: Snapshot des aktuellen Zustands, Tag setzen, BigBangBranch erstellen
* Beschreibung:
* Ziel: Den aktuellen Projektstand einfrieren, um die anschließende große Umstrukturierung nachvollziehbar zu machen.
* Schritte:
1. PR „chore: snapshot prerefactor state“ auf `main` erstellen und mergen.
2. GitTag `v0-pre-refactor` setzen.
3. Branch `refactor/big-bang-architecture` anlegen und als Arbeitsbasis verwenden.
* Artefakte:
* PRLink, TagNachweis.
* Definition of Done (DoD):
* PR gemergt, Tag `v0-pre-refactor` existiert (git log / remote tags sichtbar).
* Branch `refactor/big-bang-architecture` öffentlich verfügbar.
-27
View File
@@ -1,27 +0,0 @@
## Epic 2: RepositoryStruktur auf MeldestellenDomäne umstellen
* Zusammenfassung: Neues Skeleton und physisches Umziehen der Module/Ordner
* Beschreibung:
* Ziel: Einführen der Zielstruktur gemäß Domäne (Meldestelle, ÖTO/FEI) für Frontend, Backend und Docker.
* SollStruktur (TopLevel):
* `frontend/` (shells, features, core)
* `backend/` (gateway, discovery, services)
* `docker/` (docker-compose.yml, .env.example)
* `docs/` (adr, ARCHITECTURE.md)
* Geplanter Umzug:
* Frontend:
* `clients/app``frontend/shells/meldestelle-portal`
* `clients/shared/common-ui``frontend/core/design-system`
* `clients/shared/navigation``frontend/core/navigation`
* Backend:
* `services|domains|infrastructure|platform|core` → konsolidieren unter `backend/`:
* `backend/services/*`
* `backend/gateway`, `backend/discovery`
* Docker:
* `compose.yaml``docker/docker-compose.yml`
* Pro Service ein eigener `Dockerfile` im jeweiligen ServiceOrdner
* Definition of Done (DoD):
* VerzeichnisSkeleton vorhanden und committed.
* Dateien physisch verschoben; alle relativen Pfade in Gradle/Konfigurationen angepasst (oder bekannte Fehlerliste
dokumentiert).
* `docker/docker-compose.yml` vorhanden; `.env.example` erstellt.
-14
View File
@@ -1,14 +0,0 @@
## Epic 3: Gradle/Build Governance zentralisieren
* Zusammenfassung: Version Catalog, Settings, BuildKonventionen
* Beschreibung:
* Ziel: Einheitliche BuildBasis für alle Module (KMP/JVM), zentrale Versionierung und klare ProjektIncludes.
* Schritte:
1. `gradle/libs.versions.toml` als Single Source of Truth etablieren.
2. `settings.gradle.kts` an neue Struktur anpassen (Includes für FrontendCore, Features, Shells;
BackendServices).
3. BuildKonventionen konsolidieren (`buildSrc` oder `gradle/plugins`): Kotlin, Compose, Detekt, KTLint.
* Definition of Done (DoD):
* `./gradlew projects` zeigt die neue ModulHierarchie ohne verwaiste Einträge.
* Alle Subprojekte beziehen Versionen aus `libs.versions.toml` (keine harten Versionsnummern in Buildskripten).
* Lint/Detekt laufen in CI lokal erfolgreich.
-21
View File
@@ -1,21 +0,0 @@
## Epic 4: Frontend Core etablieren (Network, LocalDB, DesignSystem, Domain)
* Zusammenfassung: Koin/KtorClient, SQLDelight, DesignSystemKonsolidierung, Shared Domain Models
* Beschreibung:
* Ziel: Eine stabile, wiederverwendbare Basis für alle Features/Shells schaffen.
* Deliverables:
* `frontend/core/network`:
* Ktor `HttpClient` mit Plugins: Auth, Retry, JSON, Timeouts, Logging.
* KoinModule mit `single(named("apiClient"))` Export.
* `frontend/core/local-db`:
* SQLDelightSetup (KMP), SchemaOrdner, `1.sqm` Migration, BuildKonfig.
* Konvention: Keine InlineSQL in BusinessCode; Nutzung der generierten Queries.
* `frontend/core/design-system`:
* Konsolidierte `AppTheme`, Komponenten, Tokens.
* `frontend/core/domain`:
* Gemeinsame Modelle/IDs (z. B. `Turnier`, `Pruefung`, `Start`, `ReiterId`, `PferdId`, `Lizenz` …).
* Definition of Done (DoD):
* Kein Vorkommen manueller `Authorization`Header im Code (nur DI`apiClient`).
* SQLDelight generiert Code; eine DemoQuery kompiliert für das WebTarget.
* DesignSystem kompiliert; `AppTheme` nutzbar in Shell/FeatureSample.
* `core/domain` wird von Features konsumiert, ohne Feature→Feature Abhängigkeiten.
-17
View File
@@ -1,17 +0,0 @@
## Epic 5: FeaturePilot NennungsManagement (Vertical Slice)
- Zusammenfassung: Erstes Feature in Zielstruktur mit Route, UI, Data, Domain, DI
- Beschreibung:
- Ziel: Funktionsfähiger, isolierter Slice für Nennungen (Turnierbezogen), inkl. Offline/SyncPfad.
- Struktur: `frontend/features/nennungs-management/{api,ui,data,domain,di}`
- Funktionen (Minimum):
- Liste Nennungen für `turnierId` anzeigen (UI liest aus LocalDB; initialer Sync).
- Anlegen/Ändern einer Nennung lokal; Sync zum Server.
- Konfliktpfad: Bei veralteter `version` → Server 409; UI Toast + automatischer Refresh.
- Navigation/Route:
- `meldestelle/nennungen/turnier/{turnierId}` als EntryPoint.
- Definition of Done (DoD):
- Shell `meldestelle-portal` kann Route aufrufen; UI rendert Daten aus LocalDB.
- APICall nutzt DI`apiClient`.
- `lastSyncedAt` steuert FreshnessIndikator (grün/gelb/rot) im UI.
- Konfliktfall 409 nachweisbar (Mock/Backend) und UI reagiert gemäß Guideline.
-13
View File
@@ -1,13 +0,0 @@
## Epic 6: Shell MeldestellePortal (JS/Wasm)
- Zusammenfassung: Exekutierbare WebShell mit expliziten FeatureDependencies
- Beschreibung:
- Ziel: Eine Shell, die nur benötigte Features bundelt (TreeShaking wirksam) und die Navigation kapselt.
- Schritte:
1. Entry Point/Bootstrap der Shell anlegen (`frontend/shells/meldestelle-portal`).
2. Nur benötigte Features als GradleDependencies einbinden (Pilot: `nennungs-management`).
3. BundleReport aktivieren; CodeSplitting konfigurieren.
- Definition of Done (DoD):
- `./gradlew :frontend:shells:meldestelle-portal:build` erfolgreich.
- BundleReport zeigt, dass ausschließlich integrierte Features im Output sind.
- Navigation aus der Shell zu FeatureRouten funktioniert.
-18
View File
@@ -1,18 +0,0 @@
## Epic 7: BackendKonsolidierung Services & Gateway
- Zusammenfassung: Services domänenspezifisch schneiden, Gateway verankern, Compose lauffähig
- Beschreibung:
- Ziel: Minimales, aber kohärentes Backend gemäß Zielstruktur startfähig via Docker Compose.
- Services (erste Iteration):
- `entries-service` (Nennungen/Validierung),
- `results-service` (Ergebnisse),
- `scheduling-service` (Zeit/Abteilungen),
- plus `gateway` (Auth/Routing).
- Anforderungen:
- Pro Service eigener `Dockerfile` im ServiceOrdner.
- Mindestens ein Endpunkt pro Service (Stub akzeptabel), 409Konfliktpfad im `entries-service`.
- Compose: DB + Gateway + `entries-service` startbar.
- Definition of Done (DoD):
- `docker/docker-compose.yml` gestartet → Services erreichbar (Health/HTTP 200/StubDaten).
- Endpoint für 409Konflikt getestet (z. B. Postman/HTTPTest dokumentiert).
- Gateway leitet Requests korrekt weiter.
-12
View File
@@ -1,12 +0,0 @@
## Epic 8: ArchitekturGuards und CI Regeln durchsetzen
- Zusammenfassung: Detekt/KTLint Regeln, Dependency Checks, Bundle Budgets
- Beschreibung:
- Ziel: Die Architekturregeln technisch erzwingen, um Regressionen zu vermeiden.
- Guards:
- Verbot Feature→Feature Dependencies (nur → `frontend/core:*`).
- Verbot manueller `Authorization`Header im Code (nur DI`apiClient`).
- CI: DependencyGraphDiff, BundleSizeBudget je Shell, `docker compose config` Validierung.
- Definition of Done (DoD):
- CI bricht bei Verstößen, Berichte werden erzeugt.
- Auf `main` grüner Build mit aktiven Guards.
-13
View File
@@ -1,13 +0,0 @@
## Epic 9: Dokumentation & ADRs formalisieren
- Zusammenfassung: ADRTemplate, Entscheidungen festschreiben, Architecture Guide aktualisieren
- Beschreibung:
- Ziel: Entscheidungen nachvollziehbar machen und Onboarding erleichtern.
- Schritte:
1. ADRTemplate `docs/adr/ADR-000-template.md` anlegen.
2. ADRs erstellen für: Koin, SQLDelight, Optimistic Locking, FreshnessUI, CoreDomain, FeatureIsolation über
Routen.
3. `docs/ARCHITECTURE.md`: Mapping Vision ↔ Repo, Rollen, Routenbeispiele (Meldestelle, Richter, Zeitnahme).
- Definition of Done (DoD):
- Mindestens 5 ADRs vorhanden und verlinkt.
- `ARCHITECTURE.md` erlaubt Onboarding in < 30 Minuten (interner Review bestanden).
-6
View File
@@ -1,6 +0,0 @@
## Ergänzende Hinweise (Optionen)
- Labels/Tags: `architecture`, `big-bang`, `frontend`, `backend`, `docker`, `docs`.
- Priorisierung: 1 → 9 in ungefährer Reihenfolge, wobei 24 parallelisierbar sind.
- Zeitbox für Epics definieren (z. B. 12 Wochen je Epic in früher Phase) und nach jedem Epic Zwischenrelease (Tag)
setzen.
@@ -1,155 +0,0 @@
# 🏗 Project Architecture & Structure Guide
> **"Code is liability. Structure is asset."**
> Wir bauen dieses System nicht für den schnellsten Start, sondern für die **Wartbarkeit über Jahre**, Offline-Fähigkeit und Skalierbarkeit über mehrere Teams hinweg.
-----
## 1\. Die Große Übersicht: The Monorepo Strategy
Wir organisieren Backend und Frontend in einem einzigen Repository (Monorepo).
### **Warum Monorepo? (Decision Record)**
***Alternative:** Getrennte Repositories für Backend, Web-Frontend, Desktop-App.
* **Problem dabei:** "Version Hell". Backend ändert API v1 zu v2, aber Frontend-Repo ist noch auf v1. Refactorings über die ganze Kette sind schmerzhaft.
***Unsere Entscheidung:** Monorepo.
* **Atomic Commits:** Ein Pull Request enthält Backend-Änderungen UND die dazugehörige Frontend-Anpassung.
* **Single Versioning:** Wir nutzen `gradle/libs.versions.toml` als einzige Quelle der Wahrheit für Library-Versionen (z.B. Kotlin Version) über das gesamte System hinweg.
-----
## 2\. Der "Deep Dive" in die Ordnerstruktur
Hier ist der detaillierte Aufriss unseres Dateisystems. Jeder Ordner hat einen spezifischen architektonischen Zweck.
```text
/my-project-root
├── ⚙️ docker-compose.yml <-- Die lokale "Cloud". Startet DBs, Gateway & Services.
├── 📄 settings.gradle.kts <-- Definiert die Module (Frontend & Backend).
├── 📂 gradle
│ └── libs.versions.toml <-- 🛑 STOP! Hier werden Versionen definiert. Nirgendwo sonst.
├── 📂 backend <-- ARCHITEKTUR: Hexagonal / DDD
│ ├── 📂 gateway <-- Der "Türsteher". Routing & Auth-Check.
│ ├── 📂 discovery <-- Das "Telefonbuch" (Consul/Service Registry).
│ └── 📂 services <-- Die Business Logic (Microservices)
│ ├── 📂 inventory-service
│ │ ├── 📄 Dockerfile <-- Jedes Service ist ein isolierter Container!
│ │ └── 📂 src/main/kotlin/.../domain <-- Reine Logik, kein Spring!
│ └── 📂 auth-service
└── 📂 frontend <-- ARCHITEKTUR: Kotlin Multiplatform (KMP)
├── 📂 shells <-- 💡 CONCEPT: "The Assembler"
│ │ Das sind die ausführbaren Anwendungen. Sie enthalten KEINE Logik.
│ │ Sie "kleben" nur Features zusammen und konfigurieren DI.
│ │
│ ├── 📂 warehouse-app <-- Desktop-App (Windows/Linux) für Lageristen
│ │ └── build.gradle.kts (bindet :features:inventory ein)
│ └── 📂 admin-portal <-- Web-App (JS/Wasm) für Management
│ └── build.gradle.kts (bindet alle Features ein)
├── 📂 features <-- 💡 CONCEPT: "Vertical Slices" (Micro-Frontends)
│ │ Hier passiert die Arbeit. Ein Feature gehört einem Team.
│ │
│ ├── 📂 inventory-feature
│ │ ├── 📂 src/commonMain
│ │ │ ├── 📂 api <-- Public Interface (Der Vertrag nach außen)
│ │ │ ├── 📂 ui <-- Screens & Components (Internal)
│ │ │ └── 📂 data <-- Repository & SSoT (Internal)
│ │ └── build.gradle.kts
│ └── 📂 auth-feature
└── 📂 core <-- 💡 CONCEPT: "Shared Foundation"
│ Code, der sich selten ändert, aber überall genutzt wird.
├── 📂 design-system <-- UI-Baukasten (Farben, Typo, Buttons)
├── 📂 network <-- HTTP Clients & Auth-Interceptor
├── 📂 local-db <-- SQLDelight Schemas (Die Offline-Wahrheit)
└── 📂 auth <-- OAuth2 Logik (Browser Bridge für Desktop)
```
-----
## 3\. Architectural Decision Records (ADRs)
Warum haben wir das so gebaut? Hier sind die Antworten auf die "Warum nicht X?" Fragen.
### ADR 001: Kotlin Multiplatform vs. Electron / Web-Wrapper
* **Kontext:** Wir brauchen eine Web-App UND eine Desktop-App.
* **Entscheidung:** Wir nutzen **Kotlin Multiplatform (Compose)**.
* **Begründung:**
* *Performance:* Electron braucht pro App \~200MB RAM (Chromium Instanz). Unsere Desktop-Apps (Lager, Kasse) laufen auf schwacher Hardware. JVM/Native ist effizienter.
* *Type Safety:* Wir teilen Business-Logik (Validation, SSoT) zwischen Web und Desktop. Mit JS/Electron müssten wir Logik duplizieren oder transpilen.
* *Offline:* Echte SQL-Datenbank (SQLite) Integration ist in nativem Code robuster als im Browser-Storage.
### ADR 002: Multiple App Shells vs. One "Super-App"
* **Kontext:** Wir haben Lagerarbeiter, Kassierer und Manager.
* **Entscheidung:** Wir bauen **pro Rolle eine eigene "Shell"** (Executable).
* **Begründung:**
* *Security (Web):* "Tree Shaking". Wenn der Code für "Admin-User-Löschen" gar nicht erst in der `warehouse-app.js` enthalten ist, kann er auch nicht gehackt werden.
* *Focus (Desktop):* Die Lager-App startet schneller und hat weniger Bugs, weil sie den Code für das Rechnungswesen gar nicht lädt.
* *Flexibilität:* Wir können Features wiederverwenden. Das Feature `auth-feature` ist in ALLEN Apps, `inventory-feature` nur in zweien.
### ADR 003: Single Source of Truth (SSoT) via Database
* **Kontext:** Desktop-Apps werden in Hallen mit schlechtem WLAN genutzt.
* **Entscheidung:** **Database First Architecture**.
* **Begründung:**
* Klassisch (`UI -> API -> UI`) führt zu weißen Screens und Ladekreisen bei Netzschwankungen.
* Wir nutzen `UI -> Local DB <- Sync -> API`.
* Das UI zeigt **immer** Daten an (auch wenn sie 10 Minuten alt sind). Der User kann arbeiten. Sync passiert transparent im Hintergrund.
### ADR 004: Docker für alles (außer Desktop Runtime)
* **Kontext:** "Bei mir läuft's aber..." Probleme.
* **Entscheidung:** Das gesamte Backend + Web-Frontend Build-Pipeline läuft in Docker.
* **Begründung:**
* Die `docker-compose.yml` ist die Wahrheit.
* Für die Desktop-Entwicklung nutzen wir Gradle lokal, aber der Server, gegen den entwickelt wird, läuft im Container. Das garantiert Identität zwischen Dev und Prod.
-----
## 4\. Guidelines: Wo gehört mein Code hin?
Wenn du neuen Code schreibst, stelle dir diese Fragen:
### Q1: Ist es Business Logik (z.B. "Preis berechnen")?
* ➡️ Gehört in **`/backend/services/.../domain`** (Server-Side Validierung ist Pflicht).
* ➡️ UND optional in **`/frontend/features/.../domain`** (für schnelle UI-Feedback, aber Server hat das letzte Wort).
### Q2: Ist es ein UI-Element (z.B. "Runder Button")?
* ➡️ Gehört in **`/frontend/core/design-system`**.
* 🛑 *Stop\!* Baue keine Custom Buttons in deinem Feature-Ordner. Nutze das Design System. Wenn etwas fehlt, erweitere das Design System.
### Q3: Ich brauche Daten von einem anderen Service.
* **Szenario:** Im "Checkout" (Kasse) brauche ich den Produktnamen aus dem "Inventory".
***Falsch:** `CheckoutService` ruft `InventoryService` Datenbank direkt ab.
***Richtig (Backend):** `CheckoutService` ruft `InventoryService` via REST/gRPC über das Gateway.
***Richtig (Frontend):** Das `Checkout-Feature` kennt das `Inventory-Feature` nicht. Es bekommt nur eine `productId`. Wenn es Details anzeigen muss, nutzt es entweder ein eigenes minimales Datenmodell oder fragt das Backend.
### Q4: Auth Token Handling
***Niemals:** `httpClient.header("Authorization", token)` manuell aufrufen.
***Immer:** Nutze den konfigurierten Client aus dem DI-Container: `get(named("apiClient"))`. Die Architektur kümmert sich um Refresh und Injection.
-----
## 5\. Das "Mental Model" für Entwickler
Stell dir unsere App wie einen **Lego-Baukasten** vor.
1. **Core (Platte):** Das Fundament (Auth, Network, Design). Muss immer da sein.
2. **Features (Steine):** Bunte Bausteine (Inventory, Cart, Profile). Sie berühren sich seitlich nicht (keine direkten Abhängigkeiten).
3. **Shells (Modelle):** Das fertige Haus.
* Haus A (Admin Portal) nutzt alle Steine.
* Haus B (Lager App) nutzt nur die grünen Steine (Inventory).
Dein Job als Entwickler ist es meistens, **einen neuen Stein (Feature)** zu bauen oder einen bestehenden zu verbessern. Du musst dich selten um das Fundament oder das fertige Haus kümmern.