Partager les couches de dépôt et de cas d’utilisation
Déplacez la logique métier et l’accès aux données dans commonMain pour maximiser la réutilisation.
Partager les couches de dépôt et de cas d’utilisation est une leçon Kotlin Academy gratuite sur CoddyKit. Ceci est la leçon 3 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Kotlin Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Kotlin Academy comprend 4 leçons au total.
Objectif de l'architecture
Avec KMP, l'objectif est de déplacer autant de logique que possible dans commonMain : modèles de domaine, interfaces de référentiels, cas d'utilisation et ViewModels. Seules les E/S propres aux plateformes restent dans leurs ensembles de sources respectifs.
// 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)Couche domaine dans commonMain
Définissez les entités du domaine et les interfaces de référentiels dans commonMain. N'utilisez aucune importation propre à une plateforme.
// 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)
}Implémentation d'un référentiel partagé avec Ktor
Implémentez le référentiel dans commonMain à l'aide du client Ktor multiplateforme. Le moteur HTTP concret est fourni pour chaque plateforme.
// 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) }
}
}Couche des cas d'utilisation
Les cas d'utilisation orchestrent les référentiels et contiennent la logique métier. Ils sont écrits en Kotlin pur, sans dépendances propres aux plateformes.
// 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 partagé
Les ViewModels peuvent résider dans commonMain en utilisant kotlinx-coroutines et StateFlow. Android l'utilise directement ; iOS l'intègre dans 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()
}Configuration de Ktor HttpClient
Créez le HttpClient Ktor dans commonMain avec une configuration partagée. Le moteur (OkHttp / Darwin) est injecté ou créé pour chaque plateforme.
// 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 pour la persistance partagée
SQLDelight génère des requêtes Kotlin à typage sûr pour toutes les plateformes à partir des fichiers .sq de commonMain. Le pilote réel de la base de données dépend de la plateforme.
// 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()Configuration de la DI par plateforme
Assemblez tous les éléments dans un module de DI propre à la plateforme. L'interface est commune ; seuls les arguments du constructeur diffèrent.
// 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 NativeSqliteDriverTester les cas d'utilisation dans commonTest
Les cas d'utilisation n'ont aucune dépendance propre aux plateformes, ce qui permet de les tester facilement dans commonTest avec un faux référentiel.
// 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)
}
}Gestion des erreurs dans les cas d'utilisation
Utilisez les retours Result ou une classe scellée dans les cas d'utilisation pour propager les erreurs sans faire traverser d'exceptions à la frontière entre le code partagé et celui de la plateforme.
// 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) }
}Partager sans sacrifier les conventions des plateformes
Partagez la logique, pas l'interface utilisateur. Android utilise Compose ; iOS utilise SwiftUI. Les deux observent l'état du même ViewModel partagé au moyen de liaisons propres à chaque plateforme.
// 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") }Vérification rapide
Quelle couche ne doit pas contenir d'importations propres à une plateforme (NOT) dans un projet KMP bien structuré ?
Récapitulatif
Placez les modèles de domaine, les interfaces de référentiels, les cas d'utilisation et les ViewModels partagés dans commonMain. Utilisez Ktor (réseau) et SQLDelight (base de données), avec des moteurs propres aux plateformes injectés au niveau de la DI. Testez la logique métier dans commonTest avec de faux référentiels.
Questions Fréquemment Posées
La leçon « Partager les couches de dépôt et de cas d’utilisation » est-elle gratuite ?
Oui — le texte complet de « Partager les couches de dépôt et de cas d’utilisation » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Kotlin Academy, passe à CoddyKit PRO. Le cours Kotlin Academy comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Partager les couches de dépôt et de cas d’utilisation » ?
Déplacez la logique métier et l’accès aux données dans commonMain pour maximiser la réutilisation. Tu pratiques Kotlin Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Kotlin Academy ?
Aucune expérience préalable n'est requise. Kotlin Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 4.
Combien de temps prend la leçon « Partager les couches de dépôt et de cas d’utilisation » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Kotlin Academy ?
Oui. Chaque leçon Kotlin Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Structure d’un projet KMP : commonMain, androidMain, iosMain
- Mécanisme expect/actual pour les API de plateforme
- Partager les couches de dépôt et de cas d’utilisation
- Injection de dépendances dans KMP avec Koin