Summary - Backend Services (Entries, Results, Scheduling) haben Dockerfiles. - Docker Compose Orchestrierung steht (DB + Gateway + Services). - Gateway Routing für `entries-service` implementiert (StripPrefix, Path Rewrites). - Health-Checks und 409-Conflict-Demo Endpunkt verifiziert. Verification - `docker compose up --build` -> Success - `curl http://localhost:8081/api/entries` -> 200 OK (routed through Gateway) Ref: MP-27
5.1 KiB
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, 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) undViewModel(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:appsammelt 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
AppConfigumschaltbar für LAN/Dev/Prod). -
Timeout- und Logging-Handling an einer Stelle.
🔄 Datenfluss (Unidirectional Data Flow)
-
UI: Trigger Event (Button Click).
-
ViewModel/Scope: Ruft Repository auf (
launch { repo.getData() }). -
Repository: Liefert
Resource.Loading->Resource.Success(data). -
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):
-
Phase 1 (Jetzt): PingRepositoryImpl ruft direkt httpClient.get() auf.
-
Phase 2 (Offline):
Wir fügen SQLDelight (lokale DB) hinzu.
Die PingRepositoryImpl wird geändert (ohne dass die UI es merkt!):
fun getData() {
val localData = db.dao.getAll()
if (localData.isEmpty()) {
val remote = api.getAll()
db.dao.insert(remote)
return remote
}
return localData
}
- Phase 3 (Sync): Ein Hintergrund-Worker (
WorkManageroderCoroutine) 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:
-
Domain: Erstelle Horse Model und HorseRepository Interface.
-
Data: Erstelle HorseRepositoryImpl. Injiziere den HttpClient.
-
DI: Registriere das Repository im Koin-Modul.
-
UI: Erstelle HorseListScreen. Injiziere das Repository.
-
Fertig.
Diese Struktur ist sauber, skalierbar und bereit für den professionellen Einsatz beim OEPS. ✅