0Pricing
Kotlin Academy · Lekcja

Współdzielenie warstw Repository i Use Case

Przenieś logikę biznesową i dostęp do danych do commonMain, aby maksymalnie wykorzystać ponownie kod.

Współdzielenie warstw Repository i Use Case to bezpłatna lekcja Kotlin Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Kotlin Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Kotlin Academy zawiera 4 lekcji w sumie.

Cel architektury

W KMP celem jest przeniesienie jak największej części logiki do commonMain: modeli domenowych, interfejsów repozytoriów, przypadków użycia i ViewModeli. Tylko operacje wejścia-wyjścia specyficzne dla platformy pozostają w zestawach źródeł platformy.

// 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)

Warstwa domeny w commonMain

Zdefiniuj encje domenowe i interfejsy repozytoriów w commonMain. Nie używaj importów platformowych.

// 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)
}

Współdzielona implementacja repozytorium z użyciem Ktor

Zaimplementuj repozytorium w commonMain za pomocą klienta Ktor (wieloplatformowego). Rzeczywisty silnik HTTP jest dostarczany dla każdej platformy osobno.

// 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) }
    }
}

Warstwa przypadków użycia

Przypadki użycia (interaktory) koordynują pracę repozytoriów i zawierają logikę biznesową. Są napisane w czystym Kotlinie i nie mają zależności od platformy.

// 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) }
}

Współdzielony ViewModel

ViewModele mogą znajdować się w commonMain i korzystać z kotlinx-coroutines oraz StateFlow. Android używa ich bezpośrednio, a iOS opakowuje je w SwiftUI.

// 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()
}

Konfiguracja Ktor HttpClient

Utwórz obiekt Ktor HttpClient w commonMain ze współdzieloną konfiguracją. Silnik (OkHttp / Darwin) jest wstrzykiwany lub tworzony osobno dla każdej platformy.

// 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 do współdzielonego utrwalania danych

SQLDelight generuje bezpieczne typowo zapytania Kotlin dla wszystkich platform na podstawie plików .sq w commonMain. Rzeczywisty sterownik bazy danych jest specyficzny dla platformy.

// 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()

Konfiguracja DI platformy

Połącz wszystkie elementy w module DI specyficznym dla platformy. Interfejs jest wspólny, różnią się tylko argumenty konstruktora.

// 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

Testowanie przypadków użycia w commonTest

Przypadki użycia nie mają zależności od platformy, dlatego można je łatwo testować jednostkowo w commonTest za pomocą fałszywego repozytorium.

// 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)
    }
}

Obsługa błędów w przypadkach użycia

Używaj obiektów Result lub zwracaj klasy zapieczętowane z przypadków użycia, aby przekazywać błędy bez przenoszenia wyjątków przez granicę między kodem współdzielonym a platformowym.

// 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) }
}

Współdzielenie bez rezygnacji z idiomów platformy

Współdziel logikę, nie interfejs użytkownika. Android używa Compose, a iOS — SwiftUI. Obie platformy obserwują ten sam stan współdzielonego ViewModelu za pośrednictwem powiązań specyficznych dla platformy.

// 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") }

Szybkie sprawdzenie

Która warstwa nie powinna zawierać importów specyficznych dla platformy w prawidłowo zorganizowanym projekcie KMP?

Podsumowanie

Umieść modele domenowe, interfejsy repozytoriów, przypadki użycia i współdzielone ViewModele w commonMain. Używaj Ktor (sieć) i SQLDelight (baza danych), wstrzykując silniki platformowe na poziomie DI. Testuj logikę biznesową w commonTest za pomocą fałszywych repozytoriów.

Często zadawane pytania

Czy lekcja „Współdzielenie warstw Repository i Use Case” jest bezpłatna?

Tak — pełny tekst „Współdzielenie warstw Repository i Use Case” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Kotlin Academy, przejdź na CoddyKit PRO. Kurs Kotlin Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Współdzielenie warstw Repository i Use Case”?

Przenieś logikę biznesową i dostęp do danych do commonMain, aby maksymalnie wykorzystać ponownie kod. Ćwiczysz Kotlin Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Kotlin Academy?

Nie wymagamy żadnego doświadczenia. Kotlin Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Współdzielenie warstw Repository i Use Case”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Kotlin Academy?

Tak. Każda lekcja Kotlin Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Struktura projektu KMP: commonMain, androidMain, iosMain
  2. Mechanizm expect/actual dla API platform
  3. Współdzielenie warstw Repository i Use Case
  4. Wstrzykiwanie zależności w KMP za pomocą Koin
← Powrót do Kotlin Academy