Kotlin Academy · Lektion

Repository- und Use-Case-Schichten gemeinsam nutzen

Verschieben Sie Geschäftslogik und Datenzugriff nach commonMain, um sie maximal wiederzuverwenden.

Lektion 3 von 413 Schritte

Repository- und Use-Case-Schichten gemeinsam nutzen ist eine kostenlose Kotlin Academy-Lektion auf CoddyKit. Dies ist Lektion 3 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Kotlin Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Kotlin Academy-Kurs umfasst insgesamt 4 Lektionen.

Architekturziel

In KMP besteht das Ziel darin, möglichst viel Logik in commonMain zu verlagern: Domänenmodelle, Repository-Interfaces, Use Cases und ViewModels. Nur plattformspezifische I/O-Operationen verbleiben in den Plattform-Source-Sets.

// Target architecture:
// commonMain:
//   domain/    - data classes, interfaces
//   data/      - repository implementations using shared clients
//   usecase/   - business logic, composing repositories
// androidMain / iosMain:
//   DI wiring, platform drivers (DB, network)

Domänenschicht in commonMain

Definieren Sie Domänenentitäten und Repository-Interfaces in commonMain. Verwenden Sie keine Plattform-Imports.

// commonMain/domain/User.kt
data class User(val id: String, val name: String, val email: String)

// commonMain/domain/UserRepository.kt
interface UserRepository {
    suspend fun getUser(id: String): User?
    suspend fun getAllUsers(): List<User>
    suspend fun saveUser(user: User)
}

Gemeinsame Repository-Implementierung mit Ktor

Implementieren Sie das Repository in commonMain mit dem Ktor-Client (Multiplatform). Die tatsächliche HTTP-Engine wird pro Plattform bereitgestellt.

// commonMain/data/RemoteUserRepository.kt
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
class RemoteUserRepository(private val client: HttpClient) : UserRepository {
    override suspend fun getUser(id: String): User? =
        client.get("https://api.example.com/users/$id").body()
    override suspend fun getAllUsers(): List<User> =
        client.get("https://api.example.com/users").body()
    override suspend fun saveUser(user: User) {
        client.post("https://api.example.com/users") { setBody(user) }
    }
}

Use-Case-Schicht

Use Cases (Interactors) koordinieren Repositories und enthalten die Geschäftslogik. Sie bestehen aus reinem Kotlin ohne Plattformabhängigkeiten.

// commonMain/usecase/GetUserUseCase.kt
class GetUserUseCase(private val repo: UserRepository) {
    suspend operator fun invoke(id: String): Result<User> =
        runCatching { repo.getUser(id) ?: error("User not found: $id") }
}

// commonMain/usecase/SaveUserUseCase.kt
class SaveUserUseCase(private val repo: UserRepository) {
    suspend operator fun invoke(user: User): Result<Unit> =
        runCatching { repo.saveUser(user) }
}

Gemeinsames ViewModel

ViewModels können in commonMain mit kotlinx-coroutines und StateFlow angesiedelt werden. Android verwendet sie direkt; iOS bindet sie in SwiftUI ein.

// commonMain/viewmodel/UserViewModel.kt
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
class UserViewModel(private val getUser: GetUserUseCase) {
    private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
    private val _user = MutableStateFlow<User?>(null)
    val user = _user.asStateFlow()
    fun load(id: String) {
        scope.launch { _user.value = getUser(id).getOrNull() }
    }
    fun clear() = scope.cancel()
}

Ktor-HttpClient-Konfiguration

Erstellen Sie den Ktor-HttpClient in commonMain mit gemeinsamer Konfiguration. Die Engine (OkHttp / Darwin) wird pro Plattform injiziert oder erstellt.

// commonMain/network/HttpClientFactory.kt
import io.ktor.client.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
fun createHttpClient(engine: io.ktor.client.engine.HttpClientEngine? = null) =
    HttpClient(engine ?: io.ktor.client.engine.cio.CIO) {
        install(ContentNegotiation) { json() }
    }

SQLDelight für gemeinsame Persistenz

SQLDelight generiert aus .sq-Dateien in commonMain typsichere Kotlin-Abfragen für alle Plattformen. Der tatsächliche Datenbanktreiber ist plattformspezifisch.

// commonMain/db/UserQueries.sq:
// CREATE TABLE User (id TEXT NOT NULL PRIMARY KEY, name TEXT NOT NULL);
// selectAll:
// SELECT * FROM User;

// Usage in commonMain:
// val db = UserDatabase(driver)
// val users = db.userQueries.selectAll().executeAsList()

Plattformbezogene DI-Verdrahtung

Verbinden Sie alle Komponenten in einem plattformspezifischen DI-Modul. Das Interface ist gemeinsam; nur die Konstruktorargumente unterscheiden sich.

// androidMain:
val androidModule = module {
    single { AndroidSqliteDriver(UserDatabase.Schema, androidContext(), "users.db") }
    single { UserDatabase(get()) }
    single<UserRepository> { RemoteUserRepository(get()) }
    single { GetUserUseCase(get()) }
    viewModel { UserViewModel(get()) }
}

// iosMain:
// Same pattern with NativeSqliteDriver

Use Cases in commonTest testen

Use Cases haben keine Plattformabhängigkeiten und lassen sich daher in commonTest mit einem Fake-Repository einfach als Unit-Tests testen.

// commonTest/usecase/GetUserUseCaseTest.kt
import kotlin.test.Test
import kotlin.test.assertEquals
class FakeUserRepo : UserRepository {
    override suspend fun getUser(id: String) = if (id == "1") User("1", "Alice", "a@b.com") else null
    override suspend fun getAllUsers() = emptyList<User>()
    override suspend fun saveUser(user: User) {}
}
class GetUserUseCaseTest {
    private val useCase = GetUserUseCase(FakeUserRepo())
    @Test
    fun testGetExistingUser() = kotlinx.coroutines.runBlocking {
        val result = useCase("1")
        assertEquals("Alice", result.getOrNull()?.name)
    }
}

Fehlerbehandlung in Use Cases

Verwenden Sie Result oder Rückgabewerte als versiegelte Klassen in Use Cases, um Fehler weiterzugeben, ohne dass Exceptions die gemeinsame/plattformspezifische Grenze überschreiten.

// commonMain:
sealed class UserResult {
    data class Success(val user: User) : UserResult()
    data class NotFound(val id: String) : UserResult()
    data class Error(val cause: Throwable) : UserResult()
}
class SafeGetUser(private val repo: UserRepository) {
    suspend operator fun invoke(id: String): UserResult = try {
        val user = repo.getUser(id)
        if (user != null) UserResult.Success(user) else UserResult.NotFound(id)
    } catch (e: Exception) { UserResult.Error(e) }
}

Gemeinsame Nutzung ohne Verzicht auf Plattformidiome

Teilen Sie Logik, nicht die UI. Android verwendet Compose, iOS verwendet SwiftUI. Beide beobachten denselben Zustand des gemeinsamen ViewModels über plattformspezifische Bindings.

// Android (Compose):
// val user by viewModel.user.collectAsState()

// iOS (SwiftUI):
// @State var user = viewModel.user.value
// viewModel.load(id: "1")

// Shared ViewModel:
// _user.value = ... (commonMain)
fun main() { println("Share logic; use platform UI frameworks") }

Kurzer Check

Welche Schicht sollte in einem gut strukturierten KMP-Projekt KEINE plattformspezifischen Imports enthalten?

Zusammenfassung

Platzieren Sie Domänenmodelle, Repository-Interfaces, Use Cases und gemeinsame ViewModels in commonMain. Verwenden Sie Ktor (Netzwerk) und SQLDelight (Datenbank) mit Plattform-Engines, die auf der DI-Ebene injiziert werden. Testen Sie die Geschäftslogik in commonTest mit Fake-Repositories.

Kostenlos starten

Lerne Kotlin mit einem KI-Tutor — kostenlos

Schreibe und führe echten Code in deinem Browser aus, bekomme sofortige Hilfe von einem 24/7 KI-Tutor und setze dein Lernen im Web oder in der App fort.

Kurse
51
Lektionen
203

Häufig gestellte Fragen

Ist die Lektion „Repository- und Use-Case-Schichten gemeinsam nutzen“ kostenlos?

Ja — der vollständige Text von „Repository- und Use-Case-Schichten gemeinsam nutzen“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Kotlin Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Kotlin Academy-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Repository- und Use-Case-Schichten gemeinsam nutzen“?

Verschieben Sie Geschäftslogik und Datenzugriff nach commonMain, um sie maximal wiederzuverwenden. Du übst Kotlin Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um Kotlin Academy zu starten?

Keine Vorkenntnisse erforderlich. Kotlin Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 3 von 4.

Wie lange dauert die Lektion „Repository- und Use-Case-Schichten gemeinsam nutzen“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser Kotlin Academy-Lektion Code schreiben und ausführen?

Ja. Jede Kotlin Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. KMP-Projektstruktur: commonMain, androidMain, iosMain
  2. expect/actual-Mechanismus für Plattform-APIs
  3. Repository- und Use-Case-Schichten gemeinsam nutzen
  4. Dependency Injection in KMP mit Koin
← Zurück zu Kotlin Academy