Files
meldestelle/docs/clients/visionen/MP-20.md
T
stefan 114236c8d9 feat(MP-27): backend consolidation, gateway routing & service dockerfiles
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
2025-12-08 11:39:43 +01:00

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) 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!):


fun getData() {
  
  val localData = db.dao.getAll()
  
  if (localData.isEmpty()) {
    
    val remote = api.getAll()
    
    db.dao.insert(remote)
    
    return remote
  
  }
  
  return localData

}

  1. 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.