Compartir las capas de repositorio y casos de uso
Traslade la lógica de negocio y el acceso a datos a commonMain para maximizar la reutilización.
Compartir las capas de repositorio y casos de uso es una lección gratuita de Kotlin Academy en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Kotlin Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Kotlin Academy incluye 4 lecciones en total.
Objetivo de la arquitectura
En KMP, el objetivo es trasladar tanta lógica como sea posible a commonMain: modelos de dominio, interfaces de repositorio, casos de uso y ViewModels. Solo la E/S específica de la plataforma debe permanecer en los conjuntos de código fuente de cada plataforma.
// 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)Capa de dominio en commonMain
Defina las entidades de dominio y las interfaces de repositorio en commonMain. No incluya importaciones específicas de ninguna plataforma.
// 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)
}Implementación de repositorio compartida con Ktor
Implemente el repositorio en commonMain mediante el cliente Ktor (multiplataforma). El motor HTTP concreto lo proporciona cada plataforma.
// 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) }
}
}Capa de casos de uso
Los casos de uso (interactores) coordinan los repositorios y contienen la lógica de negocio. Son Kotlin puro, sin dependencias de ninguna plataforma.
// 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 compartido
Los ViewModels pueden residir en commonMain utilizando kotlinx-coroutines y StateFlow. Android los utiliza directamente; iOS los envuelve en 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()
}Configuración de Ktor HttpClient
Cree el HttpClient de Ktor en commonMain con una configuración compartida. El motor (OkHttp / Darwin) se inyecta o se crea en cada plataforma.
// 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 para persistencia compartida
SQLDelight genera consultas Kotlin con seguridad de tipos para todas las plataformas a partir de archivos .sq en commonMain. El driver de base de datos concreto es específico de cada plataforma.
// 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()Configuración de DI específica de la plataforma
Conecte todos los componentes en un módulo de DI específico de la plataforma. La interfaz es común; solo cambian los argumentos del constructor.
// 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 NativeSqliteDriverPruebas de casos de uso en commonTest
Los casos de uso no tienen dependencias de plataforma, por lo que son fáciles de probar unitariamente en commonTest mediante un repositorio falso.
// 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)
}
}Gestión de errores en los casos de uso
Utilice retornos de tipo Result o de clases selladas en los casos de uso para propagar errores sin que las excepciones crucen el límite entre el código compartido y el específico de la plataforma.
// 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) }
}Compartir sin renunciar a las convenciones de cada plataforma
Comparta la lógica, no la interfaz de usuario. Android utiliza Compose e iOS utiliza SwiftUI. Ambos observan el mismo estado del ViewModel compartido mediante enlaces específicos de cada plataforma.
// 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") }Comprobación rápida
¿Qué capa NO debería tener importaciones específicas de una plataforma en un proyecto de KMP bien estructurado?
Repaso
Coloque los modelos de dominio, las interfaces de repositorio, los casos de uso y los ViewModels compartidos en commonMain. Utilice Ktor (red) y SQLDelight (base de datos), con los motores de cada plataforma inyectados en la capa de DI. Pruebe la lógica de negocio en commonTest mediante repositorios falsos.
Aprende Kotlin con un tutor de IA — gratis
Escribe y ejecuta código real en tu navegador, obtén ayuda instantánea de un tutor de IA disponible 24/7 y continúa donde lo dejaste en la web o en la aplicación.
- Cursos
- 51
- Lecciones
- 203
Preguntas frecuentes
¿La lección «Compartir las capas de repositorio y casos de uso» es gratis?
Sí — el texto completo de «Compartir las capas de repositorio y casos de uso» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Kotlin Academy, actualiza a CoddyKit PRO. El curso de Kotlin Academy incluye 4 lecciones en total.
¿Qué aprenderé en «Compartir las capas de repositorio y casos de uso»?
Traslade la lógica de negocio y el acceso a datos a commonMain para maximizar la reutilización. Practicas Kotlin Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar Kotlin Academy?
No se requiere experiencia previa. Kotlin Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.
¿Cuánto tiempo toma la lección «Compartir las capas de repositorio y casos de uso»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de Kotlin Academy?
Sí. Cada lección de Kotlin Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Estructura de proyectos KMP: commonMain, androidMain, iosMain
- Mecanismo expect/actual para API de plataforma
- Compartir las capas de repositorio y casos de uso
- Inyección de dependencias en KMP con Koin