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:
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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. ✅
|
||||
@@ -1,14 +0,0 @@
|
||||
## Epic 1: Big‑Bang vorbereiten – Snapshot und Branching
|
||||
|
||||
* Zusammenfassung: Snapshot des aktuellen Zustands, Tag setzen, Big‑Bang‑Branch erstellen
|
||||
* Beschreibung:
|
||||
* Ziel: Den aktuellen Projektstand einfrieren, um die anschließende große Umstrukturierung nachvollziehbar zu machen.
|
||||
* Schritte:
|
||||
1. PR „chore: snapshot pre‑refactor state“ auf `main` erstellen und mergen.
|
||||
2. Git‑Tag `v0-pre-refactor` setzen.
|
||||
3. Branch `refactor/big-bang-architecture` anlegen und als Arbeitsbasis verwenden.
|
||||
* Artefakte:
|
||||
* PR‑Link, Tag‑Nachweis.
|
||||
* Definition of Done (DoD):
|
||||
* PR gemergt, Tag `v0-pre-refactor` existiert (git log / remote tags sichtbar).
|
||||
* Branch `refactor/big-bang-architecture` öffentlich verfügbar.
|
||||
@@ -1,27 +0,0 @@
|
||||
## Epic 2: Repository‑Struktur auf Meldestellen‑Domä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.
|
||||
* Soll‑Struktur (Top‑Level):
|
||||
* `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 Service‑Ordner
|
||||
* Definition of Done (DoD):
|
||||
* Verzeichnis‑Skeleton 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.
|
||||
@@ -1,14 +0,0 @@
|
||||
## Epic 3: Gradle/Build Governance zentralisieren
|
||||
|
||||
* Zusammenfassung: Version Catalog, Settings, Build‑Konventionen
|
||||
* Beschreibung:
|
||||
* Ziel: Einheitliche Build‑Basis für alle Module (KMP/JVM), zentrale Versionierung und klare Projekt‑Includes.
|
||||
* Schritte:
|
||||
1. `gradle/libs.versions.toml` als Single Source of Truth etablieren.
|
||||
2. `settings.gradle.kts` an neue Struktur anpassen (Includes für Frontend‑Core, ‑Features, ‑Shells;
|
||||
Backend‑Services).
|
||||
3. Build‑Konventionen konsolidieren (`buildSrc` oder `gradle/plugins`): Kotlin, Compose, Detekt, KTLint.
|
||||
* Definition of Done (DoD):
|
||||
* `./gradlew projects` zeigt die neue Modul‑Hierarchie ohne verwaiste Einträge.
|
||||
* Alle Subprojekte beziehen Versionen aus `libs.versions.toml` (keine harten Versionsnummern in Buildskripten).
|
||||
* Lint/Detekt laufen in CI lokal erfolgreich.
|
||||
@@ -1,21 +0,0 @@
|
||||
## Epic 4: Frontend Core etablieren (Network, Local‑DB, Design‑System, Domain)
|
||||
|
||||
* Zusammenfassung: Koin/Ktor‑Client, SQLDelight, Design‑System‑Konsolidierung, 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.
|
||||
* Koin‑Module mit `single(named("apiClient"))` Export.
|
||||
* `frontend/core/local-db`:
|
||||
* SQLDelight‑Setup (KMP), Schema‑Ordner, `1.sqm` Migration, Build‑Konfig.
|
||||
* Konvention: Keine Inline‑SQL in Business‑Code; 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 Demo‑Query kompiliert für das Web‑Target.
|
||||
* Design‑System kompiliert; `AppTheme` nutzbar in Shell/Feature‑Sample.
|
||||
* `core/domain` wird von Features konsumiert, ohne Feature→Feature Abhängigkeiten.
|
||||
@@ -1,17 +0,0 @@
|
||||
## Epic 5: Feature‑Pilot – Nennungs‑Management (Vertical Slice)
|
||||
|
||||
- Zusammenfassung: Erstes Feature in Zielstruktur mit Route, UI, Data, Domain, DI
|
||||
- Beschreibung:
|
||||
- Ziel: Funktionsfähiger, isolierter Slice für Nennungen (Turnier‑bezogen), inkl. Offline/Sync‑Pfad.
|
||||
- Struktur: `frontend/features/nennungs-management/{api,ui,data,domain,di}`
|
||||
- Funktionen (Minimum):
|
||||
- Liste Nennungen für `turnierId` anzeigen (UI liest aus Local‑DB; 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 Local‑DB.
|
||||
- API‑Call nutzt DI‑`apiClient`.
|
||||
- `lastSyncedAt` steuert Freshness‑Indikator (grün/gelb/rot) im UI.
|
||||
- Konfliktfall 409 nachweisbar (Mock/Backend) und UI reagiert gemäß Guideline.
|
||||
@@ -1,13 +0,0 @@
|
||||
## Epic 6: Shell – Meldestelle‑Portal (JS/Wasm)
|
||||
|
||||
- Zusammenfassung: Exekutierbare Web‑Shell mit expliziten Feature‑Dependencies
|
||||
- Beschreibung:
|
||||
- Ziel: Eine Shell, die nur benötigte Features bundelt (Tree‑Shaking wirksam) und die Navigation kapselt.
|
||||
- Schritte:
|
||||
1. Entry Point/Bootstrap der Shell anlegen (`frontend/shells/meldestelle-portal`).
|
||||
2. Nur benötigte Features als Gradle‑Dependencies einbinden (Pilot: `nennungs-management`).
|
||||
3. Bundle‑Report aktivieren; Code‑Splitting konfigurieren.
|
||||
- Definition of Done (DoD):
|
||||
- `./gradlew :frontend:shells:meldestelle-portal:build` erfolgreich.
|
||||
- Bundle‑Report zeigt, dass ausschließlich integrierte Features im Output sind.
|
||||
- Navigation aus der Shell zu Feature‑Routen funktioniert.
|
||||
@@ -1,18 +0,0 @@
|
||||
## Epic 7: Backend‑Konsolidierung – 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 Service‑Ordner.
|
||||
- Mindestens ein Endpunkt pro Service (Stub akzeptabel), 409‑Konfliktpfad im `entries-service`.
|
||||
- Compose: DB + Gateway + `entries-service` startbar.
|
||||
- Definition of Done (DoD):
|
||||
- `docker/docker-compose.yml` gestartet → Services erreichbar (Health/HTTP 200/Stub‑Daten).
|
||||
- Endpoint für 409‑Konflikt getestet (z. B. Postman/HTTP‑Test dokumentiert).
|
||||
- Gateway leitet Requests korrekt weiter.
|
||||
@@ -1,12 +0,0 @@
|
||||
## Epic 8: Architektur‑Guards 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: Dependency‑Graph‑Diff, Bundle‑Size‑Budget 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.
|
||||
@@ -1,13 +0,0 @@
|
||||
## Epic 9: Dokumentation & ADRs formalisieren
|
||||
|
||||
- Zusammenfassung: ADR‑Template, Entscheidungen festschreiben, Architecture Guide aktualisieren
|
||||
- Beschreibung:
|
||||
- Ziel: Entscheidungen nachvollziehbar machen und Onboarding erleichtern.
|
||||
- Schritte:
|
||||
1. ADR‑Template `docs/adr/ADR-000-template.md` anlegen.
|
||||
2. ADRs erstellen für: Koin, SQLDelight, Optimistic Locking, Freshness‑UI, Core‑Domain, Feature‑Isolation ü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).
|
||||
@@ -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 2–4 parallelisierbar sind.
|
||||
- Zeitbox für Epics definieren (z. B. 1–2 Wochen je Epic in früher Phase) und nach jedem Epic Zwischenrelease (Tag)
|
||||
setzen.
|
||||
Binary file not shown.
@@ -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.
|
||||
Reference in New Issue
Block a user