0Pricing
Kotlin Academy · Lezione

Condividere i livelli Repository e Use Case

Sposti la logica di business e l'accesso ai dati in commonMain per massimizzare il riutilizzo.

Condividere i livelli Repository e Use Case è una lezione Kotlin Academy gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Kotlin Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Kotlin Academy include 4 lezioni in totale.

Obiettivo dell'architettura

In KMP, l'obiettivo è spostare quanta più logica possibile in commonMain: modelli di dominio, interfacce dei repository, casi d'uso e ViewModel. Solo l'I/O specifico della piattaforma rimane nei source set della piattaforma.

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

Livello di dominio in commonMain

Definisca le entità di dominio e le interfacce dei repository in commonMain. Non importi elementi specifici della piattaforma.

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

Implementazione condivisa del repository con Ktor

Implementi il repository in commonMain utilizzando il client Ktor (multiplatform). Il motore HTTP effettivo viene fornito per ogni piattaforma.

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

Livello dei casi d'uso

I casi d'uso (interactor) coordinano i repository e contengono la logica di business. Sono costituiti da Kotlin puro, senza dipendenze dalla piattaforma.

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

ViewModel condiviso

I ViewModel possono risiedere in commonMain utilizzando kotlinx-coroutines e StateFlow. Android li utilizza direttamente; iOS li integra in SwiftUI tramite un wrapper.

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

Configurazione di Ktor HttpClient

Crei il Ktor HttpClient in commonMain con una configurazione condivisa. Il motore (OkHttp / Darwin) viene iniettato o creato per ogni piattaforma.

// 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 per la persistenza condivisa

SQLDelight genera query Kotlin type-safe per tutte le piattaforme a partire dai file .sq in commonMain. Il driver effettivo del database è specifico della piattaforma.

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

Collegamento della DI specifica della piattaforma

Colleghi tutti i componenti in un modulo DI specifico della piattaforma. L'interfaccia è comune; cambiano solo gli argomenti del costruttore.

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

Test dei casi d'uso in commonTest

I casi d'uso non hanno dipendenze dalla piattaforma, quindi sono facili da sottoporre a unit test in commonTest con un repository fittizio.

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

Gestione degli errori nei casi d'uso

Utilizzi restituzioni di tipo Result o di una sealed class dai casi d'uso per propagare gli errori senza che le eccezioni attraversino il confine tra codice condiviso e codice specifico della piattaforma.

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

Condividere senza rinunciare agli idiomi della piattaforma

Condivida la logica, non l'interfaccia utente. Android utilizza Compose; iOS utilizza SwiftUI. Entrambi osservano lo stesso stato del ViewModel condiviso tramite binding specifici della piattaforma.

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

Verifica rapida

Quale livello NON dovrebbe contenere import specifici della piattaforma in un progetto KMP ben strutturato?

Riepilogo

Inserisca i modelli di dominio, le interfacce dei repository, i casi d'uso e i ViewModel condivisi in commonMain. Utilizzi Ktor (rete) e SQLDelight (database), con i motori specifici della piattaforma iniettati nel livello DI. Testi la logica di business in commonTest con repository fittizi.

Domande Frequenti

La lezione «Condividere i livelli Repository e Use Case» è gratuita?

Sì — il testo completo di «Condividere i livelli Repository e Use Case» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Kotlin Academy, passa a CoddyKit PRO. Il corso Kotlin Academy include 4 lezioni in totale.

Cosa imparerò in «Condividere i livelli Repository e Use Case»?

Sposti la logica di business e l'accesso ai dati in commonMain per massimizzare il riutilizzo. Eserciti Kotlin Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Kotlin Academy?

Non è richiesta alcuna esperienza precedente. Kotlin Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.

Quanto tempo richiede la lezione «Condividere i livelli Repository e Use Case»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Kotlin Academy?

Sì. Ogni lezione Kotlin Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Struttura di progetto KMP: commonMain, androidMain, iosMain
  2. Meccanismo expect/actual per le API di piattaforma
  3. Condividere i livelli Repository e Use Case
  4. Dependency injection in KMP con Koin
← Torna a Kotlin Academy