0Pricing
Kotlin Academy · Lección

Diseñar arquitecturas de corrutinas resilientes

Combine supervisorScope, lógica de reintentos y gestores para lograr concurrencia preparada para producción.

Diseñar arquitecturas de corrutinas resilientes es una lección gratuita de Kotlin Academy en CoddyKit. Esta es la lección 4 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.

¿Qué hace que las corrutinas sean resilientes?

Las arquitecturas de corrutinas resilientes gestionan los fallos correctamente: aíslan los fallos, reintentan los errores transitorios, cancelan el trabajo obsoleto y limpian los recursos de forma determinista.

import kotlinx.coroutines.*
// Resilience pillars:
// 1. Failure isolation (SupervisorJob)
// 2. Retry with backoff (retry operator / loop)
// 3. Timeout guards (withTimeout)
// 4. Clean teardown (finally + NonCancellable)
// 5. Observability (CoroutineExceptionHandler)
fun main() = runBlocking { println("Design for failure from the start") }

Patrón de scope a nivel de aplicación

Cree un único CoroutineScope a nivel de aplicación con SupervisorJob + handler. Inyéctelo en los servicios para que todos compartan el mismo ciclo de vida.

import kotlinx.coroutines.*
object AppCoroutineScope {
    private val handler = CoroutineExceptionHandler { _, e ->
        println("[AppScope] Uncaught: ${e.message}")
    }
    val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default + handler)
    fun cancel() = scope.cancel()
}

Reintentos con retroceso exponencial

Incluya las llamadas de red en un bucle de reintentos con retroceso exponencial para gestionar correctamente los fallos transitorios.

import kotlinx.coroutines.*
suspend fun <T> retryWithBackoff(
    times: Int = 3,
    initialDelay: Long = 100,
    block: suspend () -> T
): T {
    var delay = initialDelay
    repeat(times - 1) { attempt ->
        try { return block() }
        catch (e: Exception) {
            if (e is CancellationException) throw e
            println("Attempt ${attempt+1} failed, retrying in ${delay}ms")
            delay(delay)
            delay *= 2
        }
    }
    return block()
}
fun main() = runBlocking {
    var n = 0
    val result = retryWithBackoff {
        if (n++ < 2) throw RuntimeException("transient")
        "success"
    }
    println(result)
}

Patrón de disyuntor

Un disyuntor deja de llamar a un servicio que falla después de N fallos y vuelve a abrirse tras un periodo de enfriamiento, evitando fallos en cascada.

import kotlinx.coroutines.*
class CircuitBreaker(val maxFailures: Int, val cooldownMs: Long) {
    private var failures = 0
    private var openUntil = 0L
    suspend fun <T> call(block: suspend () -> T): T {
        if (System.currentTimeMillis() < openUntil) throw RuntimeException("Circuit open")
        return try {
            val result = block()
            failures = 0
            result
        } catch (e: Exception) {
            if (e is CancellationException) throw e
            if (++failures >= maxFailures) openUntil = System.currentTimeMillis() + cooldownMs
            throw e
        }
    }
}

Establezca un tiempo de espera para cada llamada externa

Envuelva siempre la E/S externa con withTimeout o withTimeoutOrNull. Nunca permita que una conexión bloqueada mantenga una corrutina bloqueada indefinidamente.

import kotlinx.coroutines.*
suspend fun fetchWithTimeout(url: String): String? = withTimeoutOrNull(3000) {
    // ktor: client.get(url).body()
    delay(100) // simulate
    "response from $url"
}
fun main() = runBlocking {
    val result = fetchWithTimeout("https://api.example.com")
    println(result ?: "Timed out")
}

Scope estructurado por funcionalidad

Cree un CoroutineScope independiente para cada funcionalidad o pantalla, con su propio SupervisorJob. Cancélelo cuando se destruya la funcionalidad.

import kotlinx.coroutines.*
class FeatureController {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
    fun start() {
        scope.launch { /* load data */ }
        scope.launch { /* subscribe to updates */ }
    }
    fun destroy() = scope.cancel()  // cancels all feature coroutines
}

Seguridad de recursos con use()

Use use { } para los recursos AutoCloseable. Combínelo con la cancelación de corrutinas: los recursos se cierran incluso cuando se cancela la corrutina.

import kotlinx.coroutines.*
class Connection : AutoCloseable {
    override fun close() = println("Connection closed")
    suspend fun fetch(): String { delay(100); return "data" }
}
fun main() = runBlocking {
    val job = launch {
        Connection().use { conn ->
            println(conn.fetch())
        } // close() called even on cancellation
    }
    delay(50)
    job.cancelAndJoin()
}

Cierre ordenado

Implemente un cierre ordenado: deje de aceptar trabajo nuevo, espere a que terminen las corrutinas en curso y, después, cancele el scope.

import kotlinx.coroutines.*
class WorkQueue {
    private val scope = CoroutineScope(SupervisorJob())
    private val jobs = mutableListOf<Job>()
    fun submit(block: suspend () -> Unit) {
        jobs += scope.launch { block() }
    }
    suspend fun shutdown() {
        jobs.forEach { it.join() }  // wait for all
        scope.cancel()               // then cancel scope
    }
}
fun main() = runBlocking {
    val q = WorkQueue()
    repeat(3) { i -> q.submit { delay(50); println("Task $i done") } }
    q.shutdown()
    println("Queue shut down cleanly")
}

Observabilidad: CoroutineName

Etiquete las corrutinas con CoroutineName para facilitar la depuración. Los nombres aparecen en las trazas de pila y pueden usarse en los manejadores de excepciones para realizar un registro estructurado.

import kotlinx.coroutines.*
fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { ctx, e ->
        println("[${ctx[CoroutineName]?.name}] Failed: ${e.message}")
    }
    CoroutineScope(SupervisorJob() + handler).apply {
        launch(CoroutineName("UserLoader")) { throw RuntimeException("DB error") }
        launch(CoroutineName("Analytics")) { delay(100); println("Analytics ok") }
        delay(200); cancel()
    }
}

Evite GlobalScope

No use nunca GlobalScope en producción. Crea corrutinas sin padre, sin gestión del ciclo de vida y sin cancelación estructurada. Use alternativas con scope en su lugar.

import kotlinx.coroutines.*
// BAD — GlobalScope leaks coroutines:
// GlobalScope.launch { delay(Long.MAX_VALUE) }

// GOOD — scoped, cancellable:
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
scope.launch { delay(100); println("Scoped") }
fun main() = runBlocking { delay(200); scope.cancel() }

Pruebas de resiliencia

Use runTest con TestCoroutineScheduler para simular fallos, tiempos de espera y reintentos en pruebas unitarias sin usar retrasos reales.

import kotlinx.coroutines.*
import kotlinx.coroutines.test.*
fun testRetry() = runTest {
    var attempts = 0
    val result = retryWithBackoff(3, 100) {
        if (attempts++ < 2) throw RuntimeException("fail")
        "ok"
    }
    println(result) // "ok"
}
// In test, virtual time advances instantly through delays

Comprobación rápida

¿Qué combinación constituye la base de un scope de corrutinas resiliente y de larga duración?

Resumen

Las arquitecturas resilientes combinan SupervisorJob para el aislamiento, retryWithBackoff para los errores transitorios, withTimeout para la E/S bloqueada, finally/NonCancellable para la limpieza y CoroutineExceptionHandler para la observabilidad.

Preguntas frecuentes

¿La lección «Diseñar arquitecturas de corrutinas resilientes» es gratis?

Sí — el texto completo de «Diseñar arquitecturas de corrutinas resilientes» 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 «Diseñar arquitecturas de corrutinas resilientes»?

Combine supervisorScope, lógica de reintentos y gestores para lograr concurrencia preparada para producció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 4 de 4.

¿Cuánto tiempo toma la lección «Diseñar arquitecturas de corrutinas resilientes»?

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. SupervisorJob frente a Job: aislamiento de fallos
  2. CoroutineExceptionHandler: gestor global de excepciones no capturadas
  3. Propagación de excepciones en async/await
  4. Diseñar arquitecturas de corrutinas resilientes
← Volver a Kotlin Academy