Full Reactive Stack: Conclusiones
Reflexiones finales y conclusiones sobre la construcción de una aplicación Full Reactive Stack
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.
Esta guía tiene cuatro capítulos principales y un apéndice:
-
La Web Reactiva. Este capítulo te guía a través de los conceptos principales de un enfoque reactivo y lo compara con las interfaces web clásicas.
-
El backend. Esta sección cubre la creación de la aplicación Spring Boot usando las dependencias requeridas y el desarrollo de las capas del backend, prestando especial atención a los Controllers con WebFlux y el driver reactivo de MongoDB.
-
El frontend. Recorre el desarrollo de una aplicación Angular que consume el endpoint reactivo creado en el lado del servidor.
-
Conclusiones. Esta parte completa la guía, resumiendo lo que has construido y comparando los enfoques Web Reactivo y Clásico. Da algunas perspectivas sobre rendimiento, testabilidad, etc.
-
Ejecutar la app con Docker. Este apéndice te ayuda a construir y ejecutar la aplicación full-stack en Docker.
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!
En este artículo final, ejecutamos la aplicación y vemos cómo funciona todo para los dos enfoques diferentes: WebFlux con Server-Sent Events y MVC bloqueante clásico. Este post también compara esas dos alternativas en términos de experiencia de usuario, rendimiento y facilidad de desarrollo.
Desbloquea la Guía Reactiva Completa: Obtén Esta Serie de Blog y Más como un eBook Completo—¡Disponible Ahora para Comprar! Descarga el libro completo.
WebFlux vs. Bloqueante (MVC): experiencia de usuario
Comencemos nuestra comparación con la diferencia más notable para los Usuarios Web: experiencia de usuario. Usaremos el frontend para esta comparación.
Jugar el rol del usuario es bastante simple en esta aplicación: ingresar valores para los parámetros dados (paginación, número de página y tamaño) y presionar ya sea el botón ‘Reactive Request’ o el botón ‘Blocking Request’. Eso pondrá nuestra implementación en práctica, llamando a los servicios del backend desde la página web y procesando la respuesta ya sea como pequeñas porciones (WebFlux) o como una cosa completa (Web MVC). Los capítulos anteriores cubren esto, así que no discutiremos cómo funciona.
Descubrirás rápidamente que la experiencia del usuario con el caso reactivo es mucho mejor para peticiones no paginadas y tamaños de página grandes. No necesitas esperar a que la petición termine para comenzar a leer las citas. Recuerda que este es un escenario simulado en el que estamos forzando un retraso constante por elemento desde el servidor. El beneficio sería aún más notable cuando el tiempo de respuesta del servidor y la latencia de la red pueden variar impredeciblemente.
Como se mencionó en el primer capítulo, los usuarios web abandonan sitios lentos. Siguiendo el mismo ejemplo que usamos en ese momento, es mucho mejor mostrar a los clientes potenciales algunos productos de tu tienda en línea tan pronto como sea posible en lugar de esperar a que aparezca todo el contenido.
WebFlux vs. Bloqueante (MVC): rendimiento
Curiosamente, implementar un Full Reactive Web Stack introduce algo de retraso adicional en el tiempo total requerido para recuperar una respuesta del servidor. Eso se debe al procesamiento adicional necesario para hacer funcionar la comunicación. Por lo tanto, no podemos extraer conclusiones mirando las peticiones individualmente.
El enfoque Web Reactivo brilla cuando medimos el rendimiento general del servidor al manejar múltiples peticiones simultáneamente. En esta sección, exploraremos diferentes opciones de configuración de prueba y compararemos los resultados en gráficos que comparan la implementación de WebFlux con la de Web MVC.
Los resultados provienen de una prueba de benchmark que puedes ejecutar tú mismo. En la carpeta del proyecto backend de GitHub, spring-boot-reactive-web, encontrarás la clase de prueba BenchmarkTest. Elimina la anotación @Ignore y ejecuta las pruebas, o usa tu IDE para ejecutar el benchmark.
Detalles del Benchmark
Para la prueba, usé un Intel Core i7 @ 2.5 GHz con 1 procesador y 4 núcleos. El backend se ejecuta desde IntelliJ IDEA (no Docker). No modifiqué la configuración predeterminada para Netty, que, en mi caso, ejecuta 8 hilos de servidor paralelos.
El benchmark te permite configurar:
-
El número total de peticiones a ejecutar.
-
El número de peticiones a ejecutar en paralelo (paralelismo).
Cada petición realizada por el cliente debería tomar al menos un segundo ya que el retraso por elemento es de 100ms y le estamos pidiendo al servidor 10 citas. El código existente ejecuta 1, 8, 32, 96 y 768 peticiones cuatro veces:
-
Con paralelismo de 32 e invocando el endpoint reactivo.
-
Con paralelismo de 32 e invocando el endpoint bloqueante.
-
Con paralelismo de 96 e invocando el endpoint reactivo.
-
Con paralelismo de 96 e invocando el endpoint bloqueante.
Para evitar posibles diferencias en la implementación de la prueba, tanto las peticiones reactivas como las no reactivas usan un WebClient, el cliente reactivo que viene con el nuevo Spring WebFlux. También probé con RestTemplate para la parte bloqueante con resultados muy similares. Los niveles de log de la aplicación están en el repositorio para que puedas obtener información valiosa de ellos al ejecutar el Benchmark: hilos iniciados, locks adquiridos y liberados, etc.
Lado del Servidor: Peticiones Servidas por Segundo
Como puedes ver en el gráfico a continuación, WebFlux puntúa mucho mejor para una carga alta. Puede procesar casi todas las peticiones en paralelo, alcanzando 30 peticiones/segundo cuando ejecutamos 32 peticiones a la vez y 90 peticiones/segundo para 96 llamadas simultáneas.
Cuando ejecutamos llamadas bloqueantes, el servidor no puede procesar tantas peticiones en paralelo. Está claramente limitado por el número de hilos del servidor en ejecución, ocho en este caso.
Esto prueba que un enfoque Web Reactivo optimiza los recursos del servidor desbloqueando los hilos del servidor y procesando las peticiones por separado, en paralelo.
Lado del Cliente: Tiempo Promedio por Petición
Esta métrica también muestra a WebFlux como la opción ganadora. El servidor Netty mantiene todas las peticiones reactivas cerca de un mínimo de un segundo, que es en realidad la consecuencia de la capacidad del servidor para hacer frente a casi cada petición en paralelo.
En el panorama bloqueante, cuanto mayor sea el número de peticiones paralelas, más tiempo tarda la petición en completarse en promedio. Esto tiene sentido: dado que todos los hilos del servidor están ocupados, las peticiones restantes necesitan esperar, aumentando así el tiempo promedio. En el lado funcional, muchos usuarios estarían infelices porque la página web tarda mucho tiempo en cargar.
Ten en cuenta que este benchmark muestra un ganador claro porque se enfoca en un recurso clave del servidor: el número de hilos paralelos disponibles. Podríamos hacer que el stack Web clásico funcione mucho mejor:
-
Una primera opción sería aumentar el número de hilos paralelos disponibles en el servidor, ajustándolos a nuestras necesidades.
-
En el lado de la implementación, podríamos cambiar a procesamiento asíncrono en la capa del controlador. Esto podría lograrse, por ejemplo, devolviendo un
DeferredResulto unCallablecomo respuesta. Ver la sección de Peticiones Asíncronas en los documentos de Spring para más información.
WebFlux vs. Bloqueante (MVC): facilidad de desarrollo
Podemos usar este ángulo más subjetivo para comparar ambos enfoques. Sin embargo, es muy importante porque sería arriesgado elegir ciegamente WebFlux por su valor para la experiencia del usuario sin considerar otros factores.
Por ejemplo, considera el conocimiento de Patrones Reactivos en tu organización. Claramente es un cambio de paradigma, y las cosas pueden salir mal de muchas maneras si las personas no están adecuadamente capacitadas en estos conceptos. Podrías terminar con un servidor web con un rendimiento asombroso que es una pesadilla de mantener desde el punto de vista del desarrollador.
En mi opinión, la legibilidad del código puede empeorar con patrones reactivos. Largas cadenas de métodos con una indentación extraña donde el lector lucha por descifrar la lógica detrás de ellos a menos que haya buena documentación inline o muy buena asistencia de código del IDE. Además de eso, también hay complejidad adicional asociada con la depuración de estos bloques de código que no devuelven valores inmediatamente.
Adecuación
Debes evaluar si un stack Full Reactive se ajusta a tu caso. ¿Necesitas consultas SQL para completar tu respuesta Web? Malas noticias: el soporte reactivo para bases de datos relacionales todavía está en una etapa muy temprana.
Mira también el lado del cliente: ¿Pueden tus clientes web cambiar a un enfoque reactivo? Si la respuesta es no, tu lista de ventajas se volverá más corta.
Testing
Escribir pruebas Unit e Integration para streams reactivos con WebFlux puede ser complicado. La documentación de Project Reactor es un buen punto de partida, mostrando cómo puedes usar, por ejemplo, StepVerifier para verificar que tus Fluxes y Monos funcionan de la manera que quieres.
También puedes encontrar ejemplos de Unit Tests en el repositorio de GitHub de nuestra aplicación backend.
Una inconveniencia adicional es que todavía no hay una gran comunidad de desarrolladores de donde puedas ver ejemplos de testing con WebFlux. Además, las pruebas no son tan legibles como las de MVC y son más difíciles de depurar si algo sale mal.
Conclusiones
Como cualquier otra elección de tecnología, WebFlux (y el enfoque Web Reactivo en general) tiene ventajas y desventajas. No vayas por él solo porque es la nueva cosa que hacer.
Como vimos en esta guía, WebFlux puede traer beneficios de rendimiento y aprovechar la experiencia que proporcionas a tus usuarios cuando están esperando los datos de la respuesta. Por otro lado, la programación reactiva requiere nuevas formas de escribir, probar y depurar código, así que no es una transición rápida.
Siempre recomiendo un análisis exhaustivo no solo de los requisitos de tu aplicación sino también de otros aspectos de tu organización. ¿Está el equipo de desarrollo listo para el cambio? ¿Puedes resolver todos los desafíos que enfrentarás? ¿Realmente usarás los beneficios, o son solo nice-to-haves?
Para un proyecto existente que pueda sacar provecho de él, en su lugar lo probaría en un alcance limitado. Podrías implementar este enfoque en un componente separado (o módulo, o microservicio). Evalúa los resultados; luego, si ves que aporta valor, puedes refactorizar el resto del código.
Espero que hayas disfrutado esta guía. El mini-libro completo con todos los posts del blog más un capítulo extra está disponible para descarga.
Continúa Leyendo Esta Serie
Este artículo es parte de la serie Full Reactive Stack:
- Parte 1: Introducción
- Parte 2: El Backend - Spring Boot, WebFlux y MongoDB
- Parte 3: El Frontend Angular
- Parte 4: Conclusiones (este post)
¿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.