meldestelle/docs/client-data-fetching-implementation-summary-de.md
stefan 65a0084f91 docs: Migrationsplan für Projekt-Restrukturierung hinzugefügt
- 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)
2025-07-25 13:05:42 +02:00

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

  1. Clean Architecture: Klare Trennung der Verantwortlichkeiten zwischen Schichten
  2. Testbarkeit: Komponenten können isoliert getestet werden
  3. Wiederverwendbarkeit: Gemeinsame Komponenten zwischen web-app und desktop-app geteilt
  4. Typsicherheit: Stark typisierte API-Aufrufe und Antworten
  5. Fehlerbehandlung: Konsistente Fehlerbehandlung in der gesamten Anwendung
  6. 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