Guía de programación reactiva en java

6 min read
19 de junio de 2024
Guía de programación reactiva en java
10:46

La programación reactiva es un paradigma que se enfoca en flujos de datos y en el manejo asíncrono de eventos. Esto quiere decir que a través de esta forma de programación podemos construir soluciones que trabajan con datos que se actualizan a lo largo del tiempo, son capaces de detectar esas actualizaciones y además informan sobre esos cambios en la fuente de datos.

En la programación reactiva, existen tres conceptos fundamentales que trabajan en conjunto para crear flujos de datos dinámicos y sistemas receptivos: observables, observadores y suscripciones.

A continuación veremos a fondo el papel de cada uno de esos conceptos y revisaremos aspectos claves de la programación reactiva, como sus principios y métodos de transformación de datos. Al final también veremos cómo funcionan las pruebas unitarias dentro de este paradigma. Puedes ir directamente a la sección que más te interese a través de la siguiente tabla de contenidos:

  1. Observables, Observadores, suscripciones, mono y flux: conceptos fundamentales de la programación reactiva

  2. Principios de Programación reactiva

  3. Métodos de transformación de datos: qué son y para qué sirven

  4. ¿Qué son las pruebas unitarias en programación reactiva?

 

Observables, Observadores, suscripciones, mono y flux: conceptos fundamentales de la programación reactiva

La programación reactiva está diseñada desde su base para manejar notificaciones y actualizaciones de datos que ocurren en momentos impredecibles (asíncronos). Este objetivo se consigue articulando tres elementos fundamentales:

1. Observables:

Los observables son como un canal por donde fluyen los datos. Cuando hay cambios en ese flujo, como nuevos elementos, actualizaciones o incluso errores los observables emiten notificaciones que pueden ser clasificadas. 

2. Observadores:

Los observadores, también conocidos como flujos de eventos, son como los espectadores que se interesan por el flujo de datos del observable. La forma en que se vinculan a un observable es a través de lo que en programación se conoce como una suscripción. 

Además de recibir las notificaciones, los observadores pueden ejecutar acciones en consecuencia. 

3. Suscripciones:

Como ya lo dijimos, las suscripciones son el vínculo que une a los observables con los observadores. Tienen el papel de manejar la asignación y liberación de recursos asociados al flujo de datos.

 

Mono y Flux también hacen parte de los conceptos fundamentales de la programación reactiva. Se trata de tipos de datos que nos ayudan a representar flujos de datos asíncronos. La principal diferencia entre ellos radica en la cantidad de elementos que pueden emitir:

Mono:

  • Representa un flujo de datos que emite cero o un solo elemento.
  • Se utiliza comúnmente para representar operaciones que devuelven un único valor, como la recuperación de un dato de una base de datos o la realización de una validación.
  • Ejemplo: Obtener el nombre de un usuario por su ID.

Flux:

  • Representa un flujo de datos que emite cero o más elementos.
  • Se utiliza para representar operaciones que devuelven múltiples valores, como la obtención de una lista de productos de una tienda o la lectura de eventos de un sensor.
  • Ejemplo: Obtener todos los productos de una categoría

Principios de la programación reactiva

Desde interfaces de usuario fluidas hasta sistemas de procesamiento de datos en tiempo real, la programación reactiva abre un mundo de posibilidades en el desarrollo de aplicaciones modernas.

Todo esto es posible gracias a los principios a que la programación reactiva que podemos resumir en cuatro pilares: 

1. Responsividad

Los sistemas reactivos deben ofrecer tiempos de respuesta rápidos y consistentes para garantizar una experiencia de usuario fluida y receptiva.

Podemos ver un ejemplo de cómo la programación reactiva ayuda a minimizar la latencia en la forma en que gestiona eventos asíncronos. En lugar de sondear continuamente una fuente de datos para detectar cambios, la programación reactiva utiliza un mecanismo de notificaciones en el que los observables emiten notificaciones a los observadores suscritos cuando hay cambios en el flujo de datos, lo que reduce significativamente la carga del procesador y mejora la eficiencia.

2. Elasticidad

Los sistemas reactivos deben ser capaces de adaptarse a cambios en la carga de trabajo, escalando de manera horizontal para manejar picos de demanda sin comprometer el rendimiento. Esto implica que los sistemas deben poder agregar o eliminar recursos de manera dinámica en función de las necesidades del momento.

La programación reactiva permite crear sistemas que pueden manejar grandes volúmenes de datos asíncronos sin comprometer el rendimiento. Los observables pueden emitir notificaciones a múltiples observadores de manera eficiente, y los observadores pueden procesar las notificaciones de forma concurrente.

3. Resiliencia

Los sistemas reactivos deben ser robustos ante fallas y errores, configurándose y recuperándose de manera automática para garantizar la continuidad del servicio. Esto significa que los sistemas deben poder detectar, aislar y manejar fallas sin afectar a otras partes del sistema.

La programación reactiva proporciona mecanismos para manejar errores y fallos en flujos de datos asíncronos. Los observables pueden emitir notificaciones de error, y los observadores pueden definir comportamientos específicos para manejar estas situaciones. Esto permite que los sistemas se recuperen de errores y continúen funcionando de manera confiable.

4. Orientación a mensajes

Este tipo de programación también permite el desacople y la modularidad ya que los observables y observadores interactúan a través de notificaciones, lo que reduce la dependencia entre componentes. 

En programación reactiva, los componentes se comunican de manera indirecta, sin necesidad de conocer los detalles internos de los demás componentes. Esto mejora la modularidad y facilita el mantenimiento del código.

 

Métodos de transformación de datos: qué son y para qué sirven

Los métodos de transformación son funciones que se utilizan para modificar, filtrar o combinar flujos de datos. Estos métodos toman un observable como entrada y a partir de allí producen otro flujo de eventos como salida. 

A diferencia de la programación tradicional, donde los datos se procesan de forma síncrona, la programación reactiva nos ofrece un paradigma asíncrono donde los datos fluyen de forma continua a través del tiempo.

Para gestionar estos flujos dinámicos, la programación reactiva nos provee de métodos de transformación, también conocidos como operadores reactivos. Estos métodos actúan como herramientas que nos permiten:

  • map(Function<T, R>): Transforma cada elemento emitido por el Observable original en un nuevo elemento utilizando la función especificada.
  • filter(Predicate<T>): Emite sólo los elementos que cumplen con la condición especificada por el predicado.
  • flatMap(Function<T, Observable<R>>): Transforma cada elemento emitido por el Observable original en un Observable, y luego aplana los Observables resultantes en un único Observable que emite todos los elementos.
  • reduce(Function<T, R>, R): Reduce los elementos emitidos por el Observable original a un único valor utilizando la función de reducción especificada.
  • distinct(): Emite solo los elementos únicos del Observable original, eliminando los duplicados.
  • sort(Comparator<T>): Ordena los elementos emitidos por el Observable original utilizando el comparador especificado.
  • buffer(int bufferSize, int skipSize, boolean overlap): Agrupa los elementos emitidos por el Observable original en buffers de tamaño fijo, con la posibilidad de superposición entre buffers. 

En este repositorio encontrarás algunos ejercicios prácticos para aprender a utilizar diferentes métodos de transformación.

Otro elemento esencial de la programación reactiva son las pruebas unitarias, en la siguiente sección te explicamos qué son y cómo puedes utilizarlas.

¿Qué son las pruebas unitarias en programación reactiva?

Las pruebas unitarias en programación reactiva son pruebas diseñadas para evaluar el comportamiento y la funcionalidad de componentes de código como funciones, métodos o clases. 

Al permitirnos evaluar individualmente cada componente de nuestro código, las pruebas unitarias nos permiten verificar el comportamiento de nuestro código en aspectos como:

  1. Manejo de eventos: Verificar que los componentes reactivos respondan correctamente a los eventos que reciben, como flujos de datos, señales o notificaciones.
  2. Transformaciones de datos: Confirmar que las transformaciones aplicadas a los flujos de datos produzcan los resultados esperados.
  3. Gestión de estado: Asegurar que los componentes mantengan un estado coherente y correcto en respuesta a los eventos y cambios en los flujos de datos.
  4. Manejo de errores: Probar cómo manejan los componentes reactivos situaciones de error, como errores de red, excepciones o condiciones inesperadas.
  5. Comportamiento asíncrono: Evaluar cómo se comportan los componentes en situaciones de concurrencia y ejecución asíncrona.

Algunas prácticas comunes en la escritura de pruebas unitarias para programación reactiva incluyen:

  • Mocking: Utilizar herramientas de simulación (mocking) para crear versiones simuladas de objetos dependientes, como flujos de datos o servicios externos, de manera que las pruebas se puedan realizar de manera aislada.
  • Assertions: Utilizar afirmaciones (assertions) para verificar que el comportamiento observado coincida con el comportamiento esperado según la entrada proporcionada.
  • Test-driven development (TDD): Adoptar el enfoque de desarrollo dirigido por pruebas (TDD) para escribir primero las pruebas unitarias y luego desarrollar el código que satisfaga esas pruebas.
  • Testing libraries: Utilizar bibliotecas de pruebas específicas para programación reactiva que proporcionen funcionalidades y utilidades especializadas para este tipo de desarrollo, como Reactor Test para proyectos basados en Reactor o RxJava Test para proyectos basados en RxJava.

Los tests unitarios son fundamentales para asegurar que cada función y método cumplan con sus objetivos esperados de manera aislada y eficiente. Si quieres aprender más sobre pruebas unitarias te recomendamos consultar este repositorio.

Suscríbete al
Blog Pragma

Recibirás cada mes nuestra selección de contenido en Transformación digital.

Imagen form