Kotlin Academy · Lección

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.

Lección 3 de 413 pasos

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 NativeSqliteDriver

Pruebas 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.

Gratis para empezar

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

  1. Estructura de proyectos KMP: commonMain, androidMain, iosMain
  2. Mecanismo expect/actual para API de plataforma
  3. Compartir las capas de repositorio y casos de uso
  4. Inyección de dependencias en KMP con Koin
← Volver a Kotlin Academy