- Detaillierter Plan zur Migration von alter zu neuer Modulstruktur - Umfasst Überführung von shared-kernel zu core-Modulen - Definiert Migration von Fachdomänen zu bounded contexts: * master-data → masterdata-Module * member-management → members-Module * horse-registry → horses-Module * event-management → events-Module - Beschreibt Verlagerung von api-gateway zu infrastructure/gateway - Strukturiert nach Domain-driven Design Prinzipien - Berücksichtigt Clean Architecture Layering (domain, application, infrastructure, api)
6.3 KiB
Client-Datenabruf und Zustandsverwaltung - Implementierungszusammenfassung
Dieses Dokument bietet eine Zusammenfassung der clientseitigen Datenabruf- und Zustandsverwaltungsimplementierung.
Überblick
Wir haben eine umfassende Datenabruf- und Zustandsverwaltungslösung für die Client-Module implementiert. Die Implementierung folgt einem Clean-Architecture-Ansatz mit klarer Trennung der Verantwortlichkeiten zwischen den Schichten.
Hauptkomponenten
1. API-Client-Schicht
Der ApiClient-Singleton im common-ui-Modul bietet:
- Generische HTTP-Methoden (GET, POST, PUT, DELETE) für API-Anfragen
- Response-Deserialisierung mit Kotlinx Serialization
- Fehlerbehandlung mit einer benutzerdefinierten
ApiException-Klasse - Caching für GET-Anfragen mit konfigurierbarer TTL
object ApiClient {
val BASE_URL = "http://localhost:8080"
val json = Json { ignoreUnknownKeys = true; isLenient = true }
val httpClient = HttpClient(CIO) {
// Konfiguration der Kürze halber weggelassen
}
val cache = ConcurrentHashMap<String, Pair<Any, Long>>()
val CACHE_TTL = 30_000L // 30 Sekunden
suspend inline fun <reified T> get(endpoint: String, cacheable: Boolean = true): T? {
// Implementierung der Kürze halber weggelassen
return null
}
suspend inline fun <reified T> post(endpoint: String, body: Any): T {
// Implementierung der Kürze halber weggelassen
throw IllegalStateException("Nicht implementiert")
}
suspend inline fun <reified T> put(endpoint: String, body: Any): T {
// Implementierung der Kürze halber weggelassen
throw IllegalStateException("Nicht implementiert")
}
suspend inline fun <reified T> delete(endpoint: String): T {
// Implementierung der Kürze halber weggelassen
throw IllegalStateException("Nicht implementiert")
}
fun clearCache() {
cache.clear()
}
fun invalidateCache(endpoint: String) {
cache.remove(endpoint)
}
}
2. Repository-Schicht
Wir haben clientseitige Repositories implementiert, die derselben Schnittstelle wie ihre serverseitigen Gegenstücke folgen:
- Modelle: Vereinfachte clientseitige Modelle (
Person,Event) - Repository-Interfaces: Definieren den Vertrag für Datenzugriff (
PersonRepository,EventRepository) - Repository-Implementierungen: Verwenden
ApiClient, um Daten vom Backend abzurufen (ClientPersonRepository,ClientEventRepository)
Beispiel Repository-Implementierung:
class ClientPersonRepository : PersonRepository {
private val baseEndpoint = "/api/persons"
override suspend fun findById(id: String): Person? {
// Implementierung der Kürze halber weggelassen
return null
}
override suspend fun findAllActive(limit: Int, offset: Int): List<Person> {
// Implementierung der Kürze halber weggelassen
return emptyList()
}
override suspend fun findByName(searchTerm: String, limit: Int): List<Person> {
// Implementierung der Kürze halber weggelassen
return emptyList()
}
override suspend fun save(person: Person): Person {
// Implementierung der Kürze halber weggelassen
return person
}
override suspend fun delete(id: String): Boolean {
// Implementierung der Kürze halber weggelassen
return false
}
override suspend fun countActive(): Long {
// Implementierung der Kürze halber weggelassen
return 0L
}
}
3. Dependency Injection
Der AppDependencies-Singleton im web-app-Modul bietet:
- Repository-Instanzen
- Factory-Methoden zur Erstellung von ViewModels mit ordnungsgemäßen Abhängigkeiten
object AppDependencies {
private val personRepository: PersonRepository by lazy { ClientPersonRepository() }
private val eventRepository: EventRepository by lazy { ClientEventRepository() }
fun createPersonViewModel(): CreatePersonViewModel {
return CreatePersonViewModel(personRepository)
}
fun personListViewModel(): PersonListViewModel {
return PersonListViewModel(personRepository)
}
fun initialize() {
// ApiClient initialisieren, falls erforderlich
println("AppDependencies initialisiert")
}
}
4. ViewModel-Schicht
ViewModels im web-app-Modul:
- Nehmen Repositories als Konstruktor-Parameter
- Verwenden Coroutines für asynchronen Datenabruf
- Verwalten UI-Zustand (Laden, Fehler, Daten)
- Mappen Domain-Modelle zu UI-Modellen
Beispiel ViewModel:
class PersonListViewModel(
private val personRepository: PersonRepository
) {
var persons by mutableStateOf<List<PersonUiModel>>(emptyList())
private set
var isLoading by mutableStateOf(false)
private set
var errorMessage by mutableStateOf<String?>(null)
private set
init {
loadPersons()
}
fun loadPersons() {
coroutineScope.launch {
isLoading = true
errorMessage = null
try {
val personList = personRepository.findAllActive(limit = 100, offset = 0)
persons = personList.map { it.toUiModel() }
} catch (e: Exception) {
errorMessage = "Fehler beim Laden der Personen: ${e.message}"
} finally {
isLoading = false
}
}
}
// ...
}
Vorteile der Implementierung
- Clean Architecture: Klare Trennung der Verantwortlichkeiten zwischen Schichten
- Testbarkeit: Komponenten können isoliert getestet werden
- Wiederverwendbarkeit: Gemeinsame Komponenten zwischen web-app und desktop-app geteilt
- Typsicherheit: Stark typisierte API-Aufrufe und Antworten
- Fehlerbehandlung: Konsistente Fehlerbehandlung in der gesamten Anwendung
- Performance: Effizienter Datenabruf mit Caching
Zukünftige Verbesserungen
Siehe Client-Datenabruf-Verbesserungen für potenzielle zukünftige Verbesserungen.
Fazit
Die Implementierung bietet eine solide Grundlage für Datenabruf und Zustandsverwaltung in den Client-Modulen. Sie folgt Best Practices für Clean Architecture und bietet einen konsistenten Ansatz für die Datenbehandlung in der gesamten Anwendung.
Letzte Aktualisierung: 25. Juli 2025