Ya no sé programar si no es reactivo

6/12/2022 11 min.
CompetenciasFormaciónKotlinDocencia
Image noticia

La frase no es mía, es de del gran Antonio Leiva (opens new window) 👈, en una de las charlas que tenemos a menudo. Pero me ha dado pie a escribir esta entrada, que no es más que una reflexión sobre la programación reactiva y su uso en determinadas aplicaciones y cómo llevarla al terreno de la docencia para explicar sus beneficios al alumnado. ¡Ya somos dos, mi admirado DevExperto! 💪

# Reactividad: ¿esto qué es?

Vayamos por partes. O sigamos los pasos que hemos realizado en Programación de Servicios y Procesos este curso. La programación reactiva es un paradigma de programación que se basa en la reactividad. La reactividad es un concepto que se basa en la capacidad de reaccionar ante un estímulo. En el caso de la programación reactiva, se basa en la capacidad de reaccionar ante un cambio de estado. Y no es un unicornio mágico que los desarrolladores hablemos por rincones oscuros de la web, es algo más simple y sencillo que combina distintas ideas y patrones de programación para conseguir ese resultado: reaccionar ante un cambio de estado.

# ¿Qué es un cambio de estado?

Por ejemplo, si estamos en nuestro timeline de Twitter/LinkedIn o red favorita y alguien nos menciona, nos llega un mensaje, nos llega una notificación, etc. ¿Estamos constantemente preguntando si nos han mencionado para mostrar esa notificación? ¡No! De alguna manera, delegamos esa acción y somos nosotros los que reaccionamos ante ese cambio de estado.

# Patrón Observer y los eventos

Esto es tan viejo, como el patrón Observer (opens new window) o el el propio uso de los eventos. En el patrón Observer, tenemos un sujeto que es el que emite los cambios de estado y un observador que es el que reacciona ante esos cambios de estado. De hecho lenguajes como Kotlin lo tienen incorporado en el sistema en varios tipos de delegados: observable (opens new window) o vetoable (opens new window). En el caso de los eventos, tenemos un emisor de eventos y un receptor de eventos. En ambos casos, el emisor y el receptor no se conocen entre sí, pero ambos saben que el otro existe y que reaccionará ante un cambio de estado. ¿Lo ves? Lo llevas usando todo el tiempo y no lo sabías.

Imagen Imagen

Aquí te dejo una simple implementación en Kotlin de un patrón Observer por si tienes curiosidad usada en clase. Lo ves ya sabes reactividad :

// Una interfaz, podríamos usar genéricos
interface Publisher {
    fun onNews(news: String)
}

// Radio Una clase que implementa la interfaz
class RadioChannel : Publisher {
    override fun onNews(news: String) = println("La radio informa: $news")
}

// Periodico Una clase que implementa la interfaz
class Newspaper : Publisher {
    override fun onNews(news: String) = println("El periódico informa: $news")
}

// Agencia que es observada
class NewsAgency {
    // Lista de observadores, son los que implementan la interfaz
    private val listeners = mutableListOf<Publisher>()

    // Usamos los delegados que automaticamente si detectan un cambio avisan
    var news: String by Delegates.observable(initialValue = "") { _, old, new ->
        if (new != old) listeners.map { listener -> listener.onNews(new) }
    }

    // Añadimos un observador
    fun subscribe(publisher: Publisher) = listeners.add(publisher)

    // Eliminamos un observador
    fun unsubscribe(publisher: Publisher) = listeners.remove(publisher)
}

fun main() {
    // Preparamos los objetos observadores que reaccionarán ante un cambio de estado
    val radioChannel = RadioChannel()
    val newspaper = Newspaper()
    val newsAgency = NewsAgency()

    // Suscribimos a la agencia, me observan si cambio de estado
    newsAgency.subscribe(radioChannel)
    newsAgency.subscribe(newspaper)

    // Lanzamos una noticia. Al estar el delegado, la observa y automáticamente notifica
    // A mis observadores ante el cambio de estado, ¿lo ves?
    newsAgency.news = "¡Nadal Gana!"
    newsAgency.news = "¡Hoy llueve!"
    newsAgency.news = "¡Todos han aprobado!"

    // Los periódicos se retiran
    newsAgency.unsubscribe(newspaper)
    newsAgency.news = "Llegan las vacaciones :)"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

# Programación asíncrona

La programación asíncrona es un concepto que se basa en la ejecución de tareas de forma no síncrona. Esto quiere decir, que no se tiene por qué ejecutar una detrás de otras en el mismo hilo de ejecución esperando una instrucción a la anterior para poder ejecutarse. La programación asíncrona nos da la capacidad de “diferir” la ejecución de una rutina a la espera de que se complete una operación, normalmente de I/O (red, disco duro, etc.), y así evitar bloquear la ejecución hasta que se haya completado la tarea en cuestión. Esto además nos permite ejecutar varias tareas a la vez de manera concurrente, sin tener que esperar a que una de ellas termine para poder ejecutar la siguiente.

Imagen

¿Y por qué es importante la asincronía para ser reactivos? ¿Sabes cuando has de reaccionar? Por ejemplo, ¿debes bloquearte para ello si necesitas reaccionar si hay un cambio en la base de datos? Es por ello que la programación reactiva se basa en la programación asíncrona como uno de sus pilares fundamentales.

Te dejo un ejemplo de programación asíncrona en Kotlin con async/await para procesar un RSS (opens new window) :

fun main() = runBlocking<Unit> {
    measureTimeMillis {
        println("Obteniendo noticias")
        val noticiasAsync = async { getNoticias("https://www.20minutos.es/rss/") }
        print("Descargando:")
        // Esto es para que se quede bonito, pero en el fondo me está suspendiendo con el delay!!!
        while (!noticiasAsync.isCompleted) {
            print(".")
            delay(250)
        }
        println()
        println("Noticias descargadas")
        val noticias = noticiasAsync.await()
        println("Noticias obtenidas: ${noticias.size}")

        noticias.forEachIndexed { index, noticia ->
            println("Nº ${index + 1}: $noticia")
        }
    }.let { println("Tiempo total: $it ms") }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# Procesamiento de colecciones

Y sí, procesar colecciones de manera asíncrona es fundamental para la reactividad, si no, ¿cómo vas a detectar el famoso cambio de estado ante distintos valores? Si tenemos una fuente de datos como una base de datos que puede cambiar, lo ideal es reaccionar ante el cambio y procesar esos datos de manera asíncrona.

# Programación reactiva

Ahora sí estamos preparados para saber que es la programación reactiva de acuerdo a su manifiesto.

La programación reactiva (opens new window) es un paradigma enfocado en el trabajo con flujos de datos (nuestras colecciones) finitos o infinitos de manera asíncrona con el objetivo de reaccionar al cambio de estado que pueda producirse en los mismo. Su concepción y evolución ha ido ligada a la publicación del Reactive Manifesto (opens new window), que establecía las bases de los sistemas reactivos, los cuales deben ser:

  • Responsivos: aseguran la calidad del servicio cumpliendo unos tiempos de respuesta establecidos.
  • Resilientes: se mantienen responsivos incluso cuando se enfrentan a situaciones de error.
  • Elásticos: se mantienen responsivos incluso ante aumentos en la carga de trabajo.
  • Orientados a mensajes: minimizan el acoplamiento entre componentes al establecer interacciones basadas en el intercambio de mensajes de manera asíncrona.

Imagen

Programación reactiva: programación asíncrona de flujos observables

No te líes, procesas el cambio de estado observando y procesando colecciones (flujos de datos) de manera asíncrona. Es decir: patrón observador, programación asíncrona y procesamiento de colecciones en base a ideas similares al productor-consumidor y el manejo del Backpressure (se genera más elementos de los que un Subscriber puede consumir).

# Librerías de programación reactiva

Existen varias librerías de programación reactiva, te cito algunas:

Los que me conocen saben muy pro Kotlin, por lo que te dejo una sencilla implementación de Kotlin Flows para procesar un RSS (opens new window). Como puedes ver, esta vez no preguntamos activamente si hay noticias, simplemente observamos el flujo de datos y reaccionamos ante el cambio de estado asíncronamente, simplemente cuando lleguen o vayan llegando las procesamos.

fun main() = runBlocking<Unit> {
    measureTimeMillis {
        println("Obteniendo noticias")
        val noticiasFlow = getNoticiasAsFlow("https://www.20minutos.es/rss/")

        launch {
            // en una parte de la interfaz podemos actualizar las noticias cada 3 segundos
            delay(3000)
            noticiasFlow.collect {
                println("Actualización de noticias Cliente 1")
                println("Noticias Cliente 1: ${it.size}")
                it.distinct().take(10).forEachIndexed { index, noticia ->
                    println("Cliente 1 -> Noticia ${index + 1}: ${noticia.titulo}")
                }
            }
        }

        // En otra parte de la interfaz solo recoger las 10 primeras noticias una vez
        launch {
            // Otro cliente!!
            noticiasFlow.take(10).collect {
                println("Actualización de noticias Cliente 2")
                println("Noticias Cliente 2: ${it.size}")
                it.take(10).forEachIndexed { index, noticia ->
                    println("Cliente 2 -> Noticia ${index + 1}: ${noticia.titulo}")
                }
            }
        }

    }.also { println("Tiempo de ejecución: $it ms") }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# Aplicaciones de programación reactiva

Entramos en la parte interesante, ¿Cuáles son las aplicaciones de la programación reactiva? Pues si has seguido el artículo, en todo lo quue implique reaccionar ante un cambio que no sabes cuándo se producirá y por lo tanto no puedes preguntar activamente por el mismo bloqueando el hilo principal.

# Interactividad en aplicaciones

En aplicaciones de escritorio, móviles o web, la interactividad es un factor clave. La programación reactiva permite que las aplicaciones reaccionen ante los cambios de estado de manera asíncrona y sin bloquear el hilo principal. Por ejemplo, si estamos en un móvil, podemos suscribirnos a los cambios en la base de datos y si hay un cambio actualizar nuestra interfaz de usuario sin bloquear el hilo principal. Esto en librerías como Android Jetpack con Room (opens new window) es sencillo ya que podemos enganchar un flujo. De esta manera podemos suscribirnos a una api rest, una base de datos y reaccionar ante los cambios de estado mostrando cambios, notificaciones o lo que queramos de manera sencilla en nuestra interfaz.

# Servicios reactivos

Como profesor de PSP, En servicios, la ejecución asíncrona y sin bloqueo y la E/S suelen ser más rentables mediante un uso más eficiente de los recursos. Ayuda a minimizar la contención (congestión) en los recursos compartidos del sistema, que es uno de los mayores obstáculos para la escalabilidad, la baja latencia y el alto rendimiento.

Imagina un servicio que necesita realizar 10 solicitudes a una base de datos y esperar su respuestas. Digamos que cada solicitud tarda 100 milisegundos. Si necesita ejecutarlos de manera secuencial sincrónica, el tiempo total de procesamiento será de aproximadamente 1 segundo. Mientras que si es capaz de ejecutarlos todos de forma asincrónica, el tiempo de procesamiento será de solo 100 milisegundos.

Imagen Imagen

Por otro lado, sabemos que los accesos a la base de datos con JDBC por ejemplo son bloqueantes, si conseguimos trabajar con drivers reactivos como R2DBC podemos mejorar el rendimiento de nuestro servicio al no bloquear nunca la llamada.

Imagen

Con estas ideas en mente, podemos ver que la programación reactiva es una buena opción para servicios que necesiten realizar muchas operaciones y con ello no penalizar el rendimiento de nuestro servicio.

# Conclusiones

A lo largo de esta entrada he intentado acercaos a la programación reactiva, sus conceptos básicos y sus aplicaciones. No es algo tan temible y creo que podrás usarla en tus proyectos sin problemas. Para mi es fundamental en módulos como Acceso a Datos y Programación de Servicios y Procesos saber dominar los elementos claves detectando cuándo es necesaria y qué ventajas nos aporta.

Te invito a que te adentres en este mundo, sobre todo, que poco a poco lo integres en tu back o front y por supuesto en tus proyectos y clases.

Por favor si te ha gustado, comparte y comenta. Si tienes alguna duda o quieres comentar algo, puedes hacerlo en Twitter (opens new window)/LinkedIn (opens new window) o consultar proyectos para el alumnado en mi GitHub (opens new window).

Yo tampoco sé programar sin reactividad

Y sí, amigo Antonio (opens new window), yo tampoco sé programar sin reactividad y así nos va. Interfaces que reaccionan, microservicios mas ligeros, bases de datos que no bloquean, etc... Todo es reactividad.