Kotlin Academy · Lezione

Progettare architetture resilienti per le coroutine

Combini supervisorScope, logica di retry e handler per una concorrenza adatta alla produzione.

Lezione 4 di 413 passaggi

Progettare architetture resilienti per le coroutine è una lezione Kotlin Academy gratuita su CoddyKit. Questa è la lezione 4 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.

Che cosa rende resilienti le coroutine?

Le architetture resilienti basate sulle coroutine gestiscono gli errori in modo appropriato: isolano gli errori, ritentano le operazioni che falliscono temporaneamente, annullano il lavoro obsoleto e puliscono le risorse in modo deterministico.

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

Modello di scope a livello di applicazione

Crei un unico CoroutineScope a livello di applicazione con SupervisorJob + handler. Lo inietti nei servizi, così condividono tutti lo stesso ciclo di vita.

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

Nuovi tentativi con backoff esponenziale

Racchiuda le chiamate di rete in un ciclo di nuovi tentativi con backoff esponenziale per gestire in modo appropriato gli errori temporanei.

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

Modello Circuit Breaker

Un circuit breaker interrompe le chiamate a un servizio che presenta errori dopo N errori e le riattiva dopo un periodo di attesa, prevenendo gli errori a cascata.

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

Imposti un timeout per ogni chiamata esterna

Racchiuda sempre l'I/O esterno in withTimeout o withTimeoutOrNull. Non permetta mai che una connessione bloccata impedisca indefinitamente l'avanzamento di una coroutine.

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 strutturato per ogni funzionalità

Crei un CoroutineScope separato per ogni funzionalità o schermata, con il proprio SupervisorJob. Lo annulli quando la funzionalità viene eliminata.

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
}

Sicurezza delle risorse con use()

Utilizzi use { } per le risorse AutoCloseable. Lo combini con l'annullamento delle coroutine: le risorse vengono chiuse anche quando la coroutine viene annullata.

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

Arresto controllato

Implementi un arresto controllato: smetta di accettare nuovo lavoro, attenda il completamento delle coroutine in corso, quindi annulli lo 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")
}

Osservabilità: CoroutineName

Assegni un tag alle coroutine con CoroutineName per semplificare il debug. I nomi compaiono nelle tracce dello stack e possono essere usati nei gestori delle eccezioni per la registrazione strutturata.

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

Eviti GlobalScope

Non utilizzi mai GlobalScope in produzione. Crea coroutine senza un padre, senza gestione del ciclo di vita e senza annullamento strutturato. Utilizzi invece alternative basate sugli scope.

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

Testare la resilienza

Utilizzi runTest con TestCoroutineScheduler per simulare errori, timeout e nuovi tentativi nei test unitari senza ritardi reali.

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

Verifica rapida

Quale combinazione costituisce la base di uno scope di coroutine resiliente e di lunga durata?

Riepilogo

Le architetture resilienti combinano SupervisorJob per l'isolamento, retryWithBackoff per gli errori temporanei, withTimeout per l'I/O bloccato, finally/NonCancellable per la pulizia e CoroutineExceptionHandler per l'osservabilità.

Gratis per iniziare

Impara Kotlin con un tutor IA — gratis

Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.

Corsi
51
Lezioni
203

Domande Frequenti

La lezione «Progettare architetture resilienti per le coroutine» è gratuita?

Sì — il testo completo di «Progettare architetture resilienti per le coroutine» è 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 «Progettare architetture resilienti per le coroutine»?

Combini supervisorScope, logica di retry e handler per una concorrenza adatta alla produzione. 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 4 di 4.

Quanto tempo richiede la lezione «Progettare architetture resilienti per le coroutine»?

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. SupervisorJob e Job: isolamento degli errori
  2. CoroutineExceptionHandler: gestore globale delle eccezioni non catturate
  3. Propagazione delle eccezioni con async/await
  4. Progettare architetture resilienti per le coroutine
← Torna a Kotlin Academy