Full Reactive Stack – Introducción
Construye una aplicación reactiva completa de extremo a extremo con esta guía práctica que te llevará a través de un ejemplo real de aplicación web.
Este post es una versión de blog de una parte de la guía Full Reactive Stack with Spring Boot, WebFlux, Angular, RxJS, and Eventsource. El código fuente completo está en GitHub.
Resumen de la Guía
Esta guía tiene cuatro capítulos principales y un apéndice:
- Introducción (este post) - Entendiendo la programación reactiva y sus beneficios
- El Backend - Construyendo con Spring Boot, WebFlux y MongoDB
- El Frontend Angular - Creando una aplicación cliente reactiva
- Conclusiones - Comparando enfoques reactivos vs bloqueantes
- Apéndice - Recursos adicionales y referencias
Todo el código fuente (spring-boot, Angular, Docker) está disponible en GitHub: repositorio Full-Reactive Stack. Si te resulta útil, por favor dale una estrella!
Qué Vamos a Construir
Esta guía se enfoca en las capacidades de Spring WebFlux y Spring Boot 2 para crear una Aplicación Web Reactiva. Está respaldada por un ejemplo de código que puedes construir paso a paso.
Para proporcionar ejemplos realistas donde Spring no es solo el cliente de la API Reactiva, completarás el stack con una aplicación cliente en Angular 9. Para hacerla reactiva, usarás Server-Sent Events (SSE) para comunicar el backend con el frontend. Mira la figura a continuación para una vista rápida del stack que construiremos.
Esta Guía tiene como objetivo enseñar desarrollo Web Reactivo construyendo una aplicación desde cero (o navegando por el código si lo prefieres). Las tecnologías elegidas son:
- Spring Boot 2.3.
- Spring WebFlux (también Spring Web para comparar).
- Spring Data JPA Reactive Repositories
- Angular 9
- RxJS
- EventSource API.
Patrones Web Reactivos
Los patrones reactivos y las técnicas de programación no bloqueante son tecnologías ampliamente extendidas hoy en día. Sin embargo, cuando nos enfocamos en la capa Web y las APIs expuestas, la gran mayoría de las aplicaciones todavía usan estrategias bloqueantes.
Para intentar aclarar un poco mejor cómo este nuevo enfoque Web Reactivo difiere de otros, comparémoslo con dos técnicas existentes:
-
La especificación Servlet 3.0 introdujo soporte asíncrono. Eso significa que puedes optimizar el uso de los hilos del contenedor usando
Callabley elDeferredResultde Spring como respuesta de los controladores, sin embargo, desde la perspectiva del cliente, siguen siendo llamadas bloqueantes. Además, la forma imperativa de la API está lejos de ser amigable para el desarrollador, por lo que rara vez se usan. -
Desde el punto de vista del cliente web, también puedes confiar en patrones asíncronos. Esta es la forma más popular de realizar una petición al servidor, usando promesas. Los clientes usualmente realizan peticiones y esperan respuestas en diferentes hilos para evitar bloquear el flujo principal. La respuesta llega en segundo plano, y luego una función callback procesa los datos y puede actualizar el DOM del HTML. Nuevamente, aunque asíncrona, el hilo adicional también está bloqueado, esperando una respuesta que podría tardar mucho en completarse y recibiendo todos los datos simultáneamente.
En un enfoque Web Reactivo, los hilos no se bloquean hasta que todos los datos están disponibles. En su lugar, cada capa puede proporcionar datos tan pronto como los procesa. Por lo tanto, los clientes web pueden comenzar a hacer su parte antes, mejorando así la experiencia del usuario. También es más eficiente ya que las diferentes capas pueden procesar información continuamente en lugar de estar inactivas hasta que llegue un bloque completo de datos.
WebFlux y Project Reactor
Spring 5 proporciona una alternativa no bloqueante y reactiva al enfoque Spring Web MVC: Spring WebFlux. Está basado en el Project Reactor (también parte de la familia Spring), que sigue la Especificación Reactive Streams. Eso significa que WebFlux sigue los conceptos estándar de Programación Reactiva: Publishers, Subscribers, Observables, etc.
En el siguiente capítulo, cubriremos WebFlux y Project Reactor con más detalle.
Web Reactiva: Ventajas
Entonces, WebFlux implementa una API reactiva y no bloqueante. Eso suena muy bien, pero ¿qué significa exactamente? ¿Por qué deberíamos preocuparnos por las llamadas web no bloqueantes?
Manejando Conexiones Lentas
La primera razón son las conexiones lentas de Internet. Tener una conexión lenta es molesto, pero se vuelve aún peor con las llamadas HTTP bloqueantes clásicas. Supongamos que buscas los mejores teléfonos móviles en tu tienda en línea favorita. Ingresas los términos de búsqueda y presionas ‘Buscar’. Con una conexión lenta, debes esperar 15 segundos mirando un spinner o una página en blanco. Luego, la primera página completa con resultados aparece de una vez.
Eso sucede porque el servidor web no se preocupa por tu conexión lenta, por lo que está respondiendo con una -digamos- respuesta de 300 kilobytes de JSON completo con resúmenes, descripciones y precios de los primeros 50 artículos que coinciden con tus criterios. De tu lado -el lado del cliente-, el problema está relacionado con los enfoques clásicos de cliente web que usan llamadas HTTP bloqueantes (incluso en diferentes hilos), que esperan hasta que se recibe la respuesta HTTP completa para procesar los datos, y luego renderizar la página.
Una mejor alternativa sería que el servidor, en lugar de devolver los 50 artículos juntos, enviara los artículos al cliente uno por uno tan pronto como se encuentren. El cliente podría entonces adaptar su interfaz para reaccionar a estos pushes individuales de artículos y renderizarlos a medida que llegan. Cambiar solo uno de estos dos lados de la comunicación no solucionaría esta situación; ambos necesitarían usar un enfoque diferente.
Manejando Carga del Servidor
Algo similar al problema de conexión lenta sucedería si el servidor está ocupado. Imaginemos muchos usuarios buscando productos en esa tienda en línea simultáneamente, y la capacidad de la base de datos no está lista. La base de datos se vuelve más lenta, y las consultas tardan unos segundos en obtener 50 resultados. Ahora, el cuello de botella en la interacción cliente-servidor no es la interfaz web sino la conexión a la base de datos. Sin embargo, el resultado es el mismo. El usuario tiene que esperar hasta que la consulta esté completamente terminada para obtener la lista de productos.
Un mejor resultado podría lograrse si la conexión a la base de datos, en lugar de devolver resultados de consulta de una vez (bloqueante), abriera streams con los clientes de la base de datos (otra capa del backend) y devolviera resultados a medida que los encuentra.
Mejor Experiencia de Usuario
Con una interfaz web reactiva, el usuario ve tres artículos en la pantalla por segundo en lugar de todos a la vez después de quince segundos. Esa es la primera ventaja no bloqueante: disponibilidad de datos más rápida, que en nuestro ejemplo significa mejor experiencia de usuario. Nosotros, como desarrolladores, deberíamos apuntar a eso y alejarnos de las llamadas bloqueantes. Según un estudio de Google, hasta el 53% de los usuarios móviles abandonan un sitio si tarda más de 3 segundos en cargar.
En un escenario de stack completamente reactivo, la base de datos recupera resultados tan pronto como están disponibles. Lo mismo se aplica a la interfaz web (digamos HTTP). La capa de Lógica de Negocio también debe estar preparada para procesar elementos reactivamente, por ejemplo, usando bibliotecas reactivas. Finalmente, para cerrar el ciclo y beneficiarse de un stack completamente reactivo, el lado del cliente debe ser capaz de procesar los datos tan pronto como llegan, siguiendo un patrón subscriber.
Uso Mejorado de Hilos
Hay una ventaja adicional que las interfaces web reactivas comparten con otros enfoques asíncronos: mejor uso de hilos. En un sistema no bloqueante, no tienes hilos esperando a ser completados. En su lugar, el servidor estaciona el hilo del cliente rápidamente, por lo que menos hilos del servidor están ocupados. Siguiendo un patrón Publish-Subscribe, los clientes web serán notificados cuando nuevos datos estén disponibles. Esta ventaja es muy relevante si tienes una aplicación de servidor que puede funcionar lentamente a veces, acumulando así muchos hilos bloqueados y eventualmente siendo incapaz de procesar más.
¿Es No Bloqueante lo mismo que Reactivo?
Depende de a quién le hagas esa pregunta. ‘Reactivo’ es un concepto genérico que describe tu acción desencadenada por una acción previa. Podrías argumentar que las técnicas de programación que siguen un enfoque no bloqueante son estilos de programación reactiva porque usualmente utilizas una función callback que reacciona al resultado. En ese amplio alcance de la palabra ‘reactivo’, usar un CompletableFuture, o cualquier otra herramienta basada en promesas con callbacks se consideraría programación reactiva. Sin embargo, la gente comúnmente se refiere a estas bibliotecas y APIs como programación asíncrona.
Es más común hablar de programación reactiva cuando nuestro código usa uno de los frameworks existentes que siguen la especificación Reactive Streams. Algunos frameworks reactivos populares son ReactiveX (RxJS, RxJava, etc.), Akka, y Project Reactor (el usado por WebFlux). Java 9 también introdujo la especificación de reactive streams con el API Flow para que los implementadores puedan elegir los estándares de la API de Java para sus bibliotecas. Sin embargo, eso no ha funcionado hasta ahora, y la mayoría de las bibliotecas reactivas todavía usan sus clases personalizadas. En cualquier caso, todos estos frameworks implementan (con ligeras diferencias en nomenclatura) patrones como Observables, Publishers, y Subscribers.
Además, estos frameworks reactivos adoptan el concepto de backpressure. La idea principal del backpressure es que el subscriber controla el stream, por lo que el consumidor puede señalar al publisher que detenga la producción de datos en lugar de tener que acumularlos en buffers. Project Reactor implementa backpressure, como veremos y demostraremos más adelante en esta guía.
La Aplicación de Ejemplo
Para mostrar las capacidades reactivas, crearás una aplicación web cliente que recibe citas del libro Don Quijote (sí, puedes notar que soy español). Abrirás un canal de Server-Sent Events en lugar de pedir las citas usando una llamada bloqueante estándar. Si conoces sobre WebSockets, puedes verlo como una tecnología similar donde la comunicación es unidireccional (servidor a cliente).
La aplicación backend enviará estos eventos (citas) una vez que exista un subscriber (nuestra aplicación cliente). Esta parte está respaldada por WebFlux devolviendo un objeto Flux (no te preocupes por ahora por los términos; los cubriremos). En el extremo de la aplicación Angular, modelarás la conexión usando un objeto
EventSource, mapeado a un Observable<Queue> de RxJS.
El componente Angular se suscribe al Observable, añade el nuevo elemento a un array y re-renderiza la UI.
Mientras construimos el Full Reactive Stack, también crearemos un enfoque bloqueante clásico. Lo usaremos para comparar ambas estrategias desde las perspectivas de implementación y tiempo de ejecución.
Próximos Pasos
Nos sumergiremos en los detalles de implementación en el resto de los capítulos de esta guía.
Continúa Leyendo Esta Serie
Este artículo es parte de la serie Full Reactive Stack:
- Parte 1: Introducción (este post)
- Parte 2: El Backend - Spring Boot, WebFlux y MongoDB
- Parte 3: El Frontend Angular
- Parte 4: Conclusiones
¿Quieres la guía completa? Obtén Esta Serie de Blog y Más como un eBook Completo—¡Disponible Ahora para Comprar!
Get More Architecture Insights
Join hundreds of software architects and engineers getting practical insights delivered to their inbox.