Programación reactiva, un primer enfoque

Programación funcional y reactiva - Computación

Profesor: Ing. Santiago Quiñones

Docente Investigador

Departamento de Ingeniería Civil

Contenidos

Introducción a la programación reactiva

Programación reactiva

Reactiva: que produce una reacción

Acciones sobre elementos de la GUI

Aproximaciones

Programación reactiva

Pero es posible reaccionar a otros elementos

  • Recibir un correo
  • Al valor de un sensor
  • A la creación de una carpeta
  • Cuando se crea un archivo
  • Cuando se modifica un valor
  • Cuando se inserta datos en una tabla
  • Etc.

 

Otras reacciones

Programación reactiva

Manejo de datos

  • Flujo infinitos de datos
  • Se reacciona cuando llega un grupo de datos
  • Reacciones de manera asíncrona
  • Patrón observer

 

Enfoques

Programación reactiva

Functional reactive programming

  • Comportamiento o señales
  • Comportamientos son valores continuos que siempre tienen un valor actual. Ej. la posición del mouse

 

Enfoques

Patrón Observer

Patrón Observer

Patrón Observer

😔Problema

Cliente

Tienda

Patrón Observer

😊Solución

Patrón Observer

🏢Estructura

Patrón Observer

🏢Estructura

Patrón Observer en Java

Observable

Lenguajes de programación

Observable

Interfaz

public interface Observable {
    void addObserver(Observer o);

    void deleteObserver(Observer o);

    void notifyObservers();
}

Observer

Interfaz

public interface Observer {
    void update();
}

EjemploObservable

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class EjemploObservable implements Observable{
    Set<Observer> observersSet = new HashSet<>();


    @Override
    public void addObserver(Observer o) {
        observersSet.add(o);
    }

    @Override
    public void deleteObserver(Observer o) {
        observersSet.remove(o);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observersSet) {
            observer.update();
        }
    }
}

Ejemplo1Observer


public class Ejemplo1Observer implements Observer {
    @Override
    public void update() {
        System.out.println("Se ha llamada al ejemplo 1");
    }
}

Main

public class Main {
    public static void main(String[] args) {

        EjemploObservable observable = new EjemploObservable();

        observable.addObserver(new Ejemplo1Observer());
        
        observable.notifyObservers();

        
    }
}

Reactive Streams

Reactive Streams

Definición

Iniciativa para proporcionar un estándar para el procesamiento de flujos de datos de una forma:

  • Asíncrona
  • Back pressure (contrapresión)
  • Sin bloqueo

Reactive Streams

Flujo de datos

Reactive Streams

Flujo de datos

Reactive Streams

Síncrono

Reactive Streams

Asíncrono

Reactive Streams

Back pressure

Quién consume los datos está en la capacidad de señalar cuántos datos puede recibir.

Reactive Streams

Sin bloqueo

El procesamiento se hace sin bloqueos.

Ejemplos:

  • Computador procesa usando Threads
  • Thread puede dedicarse a una tarea específica
  • Thread mientras espera, puede dedicarse a otras actividades

Reactive Streams

Lenguaje de programación

https://reactivex.io

ReactiveX

An API for asynchronous programming with observables streams

Reactive Streams

Lenguaje de programación

Algunos programas tienen sus propias implementaciones para manejar reactive streams. 

Por ejemplo: Java

  • Desde Java 9 existe un soporte nativo para reactive streams.
  • java.util.current.Flow
    • Publisher - suscribe(), submit(), close()
    • Subscriber - onSuscribe(), onNext(), onError(), y onComplete()

Reactive Streams

Lenguaje de programación

En Scala

  • En Scala se puede trabajar con RxScala, Akka Streams, Monix, y ZIO
  • Para trabajar con RxScala en IntellyJ es necesario agregar la siguiente dependencia en build.sbt

 

libraryDependencies += "io.reactivex" %% "rxscala" % "0.27.0"

ReactiveX en Scala

RxScala

Trabajar con RxScala

Instalar la dependencia de RxScala: RxScala está basado en RxJava, por lo que necesitas agregar la dependencia a tu archivo build.sbt:

libraryDependencies += "io.reactivex" %% "rxscala" % "0.27.0"

Características principales

  • Observables: Emiten datos de forma sincrona o asincrona
  • Operadores: Permiten transformar, filtrar y combinar flujos de datos. 
  • Schedulers: Ayudan a manejar hilos de ejecución y concurrencia.

Ejemplo Básico: Crear un observable

Observable que emite una secuencia de valores

import rx.lang.scala.Observable

object BasicExample extends App {
  // Crear un Observable con una lista de valores
  val observable = Observable.just(1, 2, 3, 4, 5)

  // Suscribirse al Observable
  observable.subscribe(
    onNext = x => println(s"Received: $x"),        // Procesar cada valor emitido
    onError = e => println(s"Error: ${e.getMessage}"), // Manejar errores
    onCompleted = () => println("Completed!")        // Notificar cuando el flujo termine
  )
}



Clase principal para manejar flujos de datos reactivos

Crea un Observable que emite una lista de valores

Activa el Observable y le indica qué hacer con los datos que emite

Transformar datos con map

Multiplicar cada valor emitido por 2

import rx.lang.scala.Observable

object MapExample extends App {
  // Crear un Observable con valores
  val observable = Observable.just(1, 2, 3, 4, 5)

  // Aplicar transformación a cada valor
  observable
    .map(_ * 2) // Multiplica cada valor por 2
    .subscribe(x => println(s"Transformed: $x"))
}




Filtrar valores con filter

Mostrar sólo los números mayores a 3.

import rx.lang.scala.Observable

object FilterExample extends App {
  // Crear un Observable con valores
  val observable = Observable.just(1, 2, 3, 4, 5)

  // Filtrar valores mayores a 3
  observable
    .filter(_ > 3) // Filtrar números mayores a 3
    .subscribe(x => println(s"Filtered: $x"))
}




Flujos Asíncronos con temporizadores

Emitir un valor cada segundo

import rx.lang.scala.Observable

// Proporciona utilidades para trabajar con intervalos de tiempo
import scala.concurrent.duration._

object IntervalExample extends App {
  // Crear un Observable que emite valores cada segundo
  val observable = Observable.interval(1.second).take(5) // Tomar 5 valores

  // Suscribirse al flujo
  observable.subscribe(
    x => println(s"Tick: $x"), // Procesar cada emisión
  )

  // Mantener la aplicación en ejecución para observar los valores
  Thread.sleep(6000)
}





Crea un Observable que emite valores (0, 1, 2, ...) con un intervalo de 1 segundo entre cada emisión

Combinar flujos con merge

Combinar dos flujos diferentes

import rx.lang.scala.Observable

object MergeExample extends App {
  // Crear dos Observables
  val observable1 = Observable.just("A", "B", "C")
  val observable2 = Observable.just("1", "2", "3")

  // Combinar los dos flujos
  Observable.merge(observable1, observable2)
    .subscribe(x => println(s"Received: $x"))
}





Manejo de errores

Continuar el flujo incluso si ocurre un error.

import rx.lang.scala.Observable

object ErrorHandlingExample extends App {
  // Crear un Observable que puede generar un error
  val observable = Observable.just(10, 5, 0, 4)

  // Manejar errores en el flujo
  observable
    .map(x => 10 / x) // Esto generará un error al dividir por 0
    .onErrorResumeNext(_ => Observable.just(-1)) // Continuar con un valor predeterminado
    .subscribe(
      x => println(s"Received: $x"),
      e => println(s"Error: ${e.getMessage}"),
      () => println("Stream completed")
    )
}






Simulación de un sensor

Emitir valores aleatorios como si fueran datos de un sensor.

import rx.lang.scala.Observable
import scala.util.Random
import scala.concurrent.duration._

object SensorExample extends App {
  // Crear un flujo de datos del sensor
  val sensorStream = Observable.interval(1.second).map(_ => Random.between(20.0, 30.0))

  // Procesar el flujo del sensor
  sensorStream
    .take(5) // Emitir solo 5 valores
    .subscribe(
      x => println(f"Sensor reading: $x%.2f°C"),
      e => println(s"Error: ${e.getMessage}"),
      () => println("Sensor stopped")
    )

  // Mantener la aplicación corriendo
  Thread.sleep(6000)
}

Combinar datos de sensores con zip

Combinar lecturas de dos sensores en un flujo único



import rx.lang.scala.Observable
import scala.util.Random
import scala.concurrent.duration._

object ZipExample extends App {
  // Crear flujos de dos sensores
  val sensor1 = Observable.interval(1.second).map(_ => Random.between(20, 30))
  val sensor2 = Observable.interval(1.second).map(_ => Random.between(30, 40))

  // Combinar los flujos con zip
  sensor1.zip(sensor2)
    .take(5)
    .subscribe(
      onNext = { case (temp1, temp2) => println(s"Sensor1: $temp1°C, Sensor2: $temp2°C") },
      onError = e => println(s"Error: ${e.getMessage}"),
      onCompleted = () => println("Stream completed!")
    )

  // Mantener la aplicación corriendo
  Thread.sleep(6000)
}

Combinar datos de sensores con zip

Combinar lecturas de dos sensores en un flujo único



import rx.lang.scala.Observable
import scala.util.Random
import scala.concurrent.duration._

object ZipExample extends App{
  // Crear flujos de dos sensores
  val sensor1 = Observable.interval(1.second).map(_ => Random.between(20, 30))
  val sensor2 = Observable.interval(1.second).map(_ => Random.between(30, 40))

  // Combinar los flujos con zip
  sensor1.zipWith(sensor2) { (temp1, temp2) =>
    println(s"Sensor1: $temp1°C, Sensor2: $temp2°C")
  }.take(5).subscribe()

  // Mantener la aplicación corriendo
  Thread.sleep(6000)
}

Sistema de alertas

Generar una alerta si un valor supera un umbral.

import rx.lang.scala.Observable
import scala.util.Random
import scala.concurrent.duration._

object AlertExample extends App{
  val observable = Observable.interval(1.second).map(_ => Random.between(20.0, 40.0)) // Datos aleatorios

  observable
    .take(10) // Emitir 10 valores
    .subscribe(
      temp => {
        println(f"Temperature: $temp%.2f°C")
        if (temp > 35) println(f"🚨 ALERT! High temperature detected: $temp%.2f°C")
      },
      e => println(s"Error: ${e.getMessage}"),
      () => println("Monitoring stopped")
    )

  Thread.sleep(12000)
}