Spring WebFlux es uno de los nuevos frameworks que vienen con la nueva versión de Spring 5. ¿Para qué sirve exactamente Spring WebFlux? . Vamos a repasar algunos de los conceptos fundamentales en los cuales Spring Framework se ha basado en sus últimas versiones. Para empezar vamos a revisar un poco Spring MVC que en su momento fue un avance importante y substituyo en muchos lugares a Struts como framework de capa de presentación.
Este framework adquirió madurez en la versión 3 de Spring Framework . Siempre se ha usado para construir aplicaciones Web MVC clásicas
Estas aplicaciones se construyeron de forma masiva ya que aportaban orden en la construcción de aplicaciones web y la facilidad de acceder a ellas desde cualquier navegador y lugar.
Spring 4 y REST
Con el paso del tiempo se necesitaron arquitecturas más flexibles en las cuales se priorizara la construcción de servicios REST y cualquier tipo de cliente se pudiera conectar a ellas .
Podría ser una aplicación Web , una aplicación móvil o otra aplicación que hiciera el rol de cliente aunque fuera una aplicación de lado servidor . La publicación se servicios REST abre las puertas a la flexibilidad a nivel de el tipo de clientes.
Es en este tipo de aplicaciones en las cuales es habitual por ejemplo encontrarnos un cliente de Angular o React ya que acceden a servicios REST que publican datos en formato JSON y los usan para presentar la información al usuario.
REST y Clientes
Hoy en día nos encontramos que una parte importante de los clientes a nivel de REST pueden ser clientes que necesitan consultar una información puntual de nuestra aplicación a nivel de REST . Es decir no necesitan acceder a todo el abanico de funcionalidad que publicamos sino que necesitan un pequeño subconjunto.
REST ,Combinando peticiones
Con este tipo de arquitecturas es muy habitual tener la necesidad que por ejemplo nuestra aplicación cliente de Spring necesite acceder a servicios REST de multiples aplicaciones que en este caso hacen el rol de aplicaciones servidoras.
Spring WebFlux
Es aquí donde hay un salto en cuanto a como podemos tratar este tipo de arquitectura. ¿Podemos usar Spring 4 de forma clásica? . Esta claro que si y que puede publicar servicios REST y puede consumirlos con REST Templates . Ahora bien existe un problema si cada petición tarda 3 segundos en hacerse a nivel de REST y tenemos que realizar las 6 peticiones que aparecen en el diagrama . Habrá que sumar los tiempos de cada una ya que se trata de lo que habitualmente se conoce como peticiones bloqueantes es decir yo realizo una petición y espero a su resultado para poder realizar la siguiente :
Esto es algo que Spring 4 no tiene muchas capacidades para resolver . Sin embargo existen en muchas casuisticas en las cuales nos gustaría realizar todas las peticiones de forma simultanea y en cuanto tengamos el resultado de todas dar por finalizada la tarea. Es decir si cada petición tarda 3 segundos y hacemos todas a la vez . En 3 segundos tendremos todos los datos ya en nuestra aplicación y no en 18.
La ventaja es clara . Es este tipo de situaciones en las cuales Spring WebFlux brilla ya que nos provee de un framework no bloqueante . Vamos a construir un ejemplo que nos ayude a entender las cosas. En primer lugar vamos a utilizar un proyecto servidor que nos devuelva una lista de facturas utilizando Spring Boot . En él únicamente añadiremos la dependencia Web y Reactive Web a continuación se muestra el pom.xml.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.arquitecturajava</groupId> <artifactId>servidor</artifactId> <version>0.0.1-SNAPSHOT</version> <name>servidor</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.arquitecturajava</groupId> <artifactId>dominio</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Una vez tenemos configurado Spring Boot vamosa crear un @RestController .
package com.arquitecturajava.servidor; import java.util.ArrayList; import java.util.List; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import com.arquitecturajava.dominio.Factura; @RestController public class FacturasController { @GetMapping("/facturas") public List<Factura> buscarTodas() { List<Factura> lista= new ArrayList<Factura>(); lista.add(new Factura(1,"ordenador",200)); lista.add(new Factura(2,"tablet",300)); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return lista; } }
Este controlador nos devolverá una lista de Facturas . Esta lista se encuentra en otro proyecto que se denomina dominio.
package com.arquitecturajava.dominio; public class Factura { private int numero; private String concepto; private double importe; public int getNumero() { return numero; } public void setNumero(int numero) { this.numero = numero; } public String getConcepto() { return concepto; } public void setConcepto(String concepto) { this.concepto = concepto; } public double getImporte() { return importe; } public void setImporte(double importe) { this.importe = importe; } public Factura(int numero, String concepto, double importe) { super(); this.numero = numero; this.concepto = concepto; this.importe = importe; } public Factura() { super(); } }
Una vez que lo tenemos configurado , arrancamos el proyecto de Boot y accedemos a la URL que nos tardara en mostrar la lista unos 3 segundos.
Spring Cliente RestTemplate
Es momento de construir otro proyecto con Spring Boot y RestTemplates y acceder a los datos para imprimirlo en una página HTML . Para ello crearemos un proyecto de SpringBoot similar al anterior pero que incluya las dependencias de ThymeLeaf
En este proyecto nos crearemos una clase de servicio que se encargue de invocar a la url de /facturas y un controlador que la invoque varias veces seguidas.
package com.arquitecturajava.cliente; import java.util.Arrays; import java.util.List; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import com.arquitecturajava.dominio.Factura; @Service public class ClienteFacturasService { public List<Factura> buscarTodas() { RestTemplate plantilla = new RestTemplate(); Factura[] facturas = plantilla.getForObject("http://localhost:8080/facturas", Factura[].class); List<Factura> lista = Arrays.asList(facturas); return lista; } }
package com.arquitecturajava.cliente; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import com.arquitecturajava.dominio.Factura; @Controller public class FacturaClienteController { @Autowired ClienteFacturasService servicio; @RequestMapping("/lista") public String lista(Model modelo) { List<Factura> lista= new ArrayList<Factura>(); lista.addAll(servicio.buscarTodas()); lista.addAll(servicio.buscarTodas()); lista.addAll(servicio.buscarTodas()); modelo.addAttribute("lista", lista); return "lista"; } }
Una vez realizada esta operación nos queda de ver el código de la plantilla y ejecutar:
<table> <thead> <tr> <th> id </th> <th> concepto </th> <th> importe </th> </tr> </thead> <tbody> <tr th:each="factura : ${lista}"> <td><span th:text="${factura.numero}"> </span></td> <td><span th:text="${factura.concepto}"></span></td> <td><span th:text="${factura.importe}"></span></td> </tr> </tbody> </table>
No nos sorprenderá mucho que tarda más de 9 segundos en renderizar la tabla con los datos en la web.
Spring WebFlux al rescate
Cuando las aplicaciones comienzan a funcionar con este enfoque en las cuales unos servicios REST invocan a otros y estos a otros con combinaciones etc . El problema del bloqueo se hace más y más evidente. Es aquí donde podemos usar Spring WebFlux. El framework nos permitirá construir un servicio REST no bloqueante de tal forma que podamos hacer varias peticiones simultaneas y las procese en paralelo. Vamos a ver el código del Controlador REST.
package com.arquitecturajava.servidor; import java.time.Duration; import java.util.ArrayList; import java.util.List; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import com.arquitecturajava.dominio.Factura; import reactor.core.publisher.Flux; @RestController public class FacturasController { @GetMapping("/facturas") public Flux<Factura> buscarTodas() { Flux<Factura> lista= Flux.just(new Factura(1,"ordenador",200) ,new Factura(2,"tablet",300)).delaySequence(Duration.ofSeconds(3)); return lista; } }
Como se puede observar hace uso de un objeto de tipo Flux . Este tipo representa una lista asíncrona de objetos. Ademas obligamos a esperar 3 segundos antes de devolver la lista. Con esto abrimos las capacidades noblocking de Spring.
El siguiente paso es ver como construimos el cliente . En este caso usaremos un servicio que se encargará de realizar como en el caso anterior tres peticiones.
import org.springframework.web.reactive.function.client.WebClient; import com.arquitecturajava.dominio.Factura; import reactor.core.publisher.Flux; @Service public class ClienteFacturasService { public Flux<Factura> buscarTodas() { WebClient cliente = WebClient.create("http://localhost:8080/facturas"); Flux<Factura> facturas=cliente.get().retrieve().bodyToFlux(Factura.class); Flux<Factura> facturas2=cliente.get().retrieve().bodyToFlux(Factura.class); Flux<Factura> facturas3=cliente.get().retrieve().bodyToFlux(Factura.class); Flux<Factura> todas=Flux.merge(facturas,facturas2,facturas3); System.out.println(todas); return todas; } }
Estas tres peticiones se realizan a traves de un WebClient que es el nuevo objeto de Spring 5 que permite programación reactiva que no bloquea. Convertimos cada una de las peticiones en un objeto Flux y las fusionamos con el método merge . Realizado este último paso devolvemos la estructura para que un controlador pueda asignarlo a una plantilla.
package com.arquitecturajava.cliente; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.thymeleaf.spring5.context.webflux.IReactiveDataDriverContextVariable; import org.thymeleaf.spring5.context.webflux.ReactiveDataDriverContextVariable; import com.arquitecturajava.dominio.Factura; @Controller public class FacturaClienteController { @Autowired ClienteFacturasService servicio; @RequestMapping("/lista") public String lista(final Model modelo) { List<Factura> lista=servicio.buscarTodas().collectList().block(); modelo.addAttribute("lista", lista); return "lista"; } }
Acabamos de construir nuestro primer ejemplo con Spring WebFlux la lista solo tardará 3 segundos en cargarse.
Es el tiempo que tardan todas las peticiones en ejecutarse de forma simultanea. Eso sí nadie nos asegura el orden de llegada de los datos .
las funcionalidades de webFlux me podrian servir para video bajo demanda? es decir varios usuarios consumiendo recursos a la vez??
Buenas muy bueno su post pero tengo una consulta para que utiliza el método block() ?
para bloquear el flujo asincrono y que devuelva un resultado
Q sencillo se explicó! muchas gracias!
gracias 🙂
Excelente explicacion de webflux!!muchas gracias por compartir este conocimiento.
de nada 🙂
Hola Cecilio, una pregunta, que ventaje me aporta WebClient de Spring WebFlux, si con Spring MVC
pudiera usar RestTemplate con @Async y CompletableFuture.allOf().join() y debería tener el mismo resultado no.
Saludos desde Argentina…
Hola Daniel
Lo que aporta es una capacidad más fuerte de gestión asíncrona, es decir lo que usabas de Spring MVC no esta orientado a programación Reactiva en cambio WebClient si. En una situación sencilla podrías hacer lo que tu dices en algo más complejo como websockets o server send events o peticiones REST complejas tendrías limitaciones
Hola Cecilio, para hacer la prueba en Local , tendriamos que cambiar los puertos , no ?, por ejemplo el de servicio en vez de WebClient cliente = WebClient.create(“http://localhost:8080/facturas”);
a
WebClient cliente = WebClient.create(“http://localhost:8081/facturas”);
Gracias
si claramente 🙂
Que tal, excelte articulo, para mi entender en mi camino con WEBFLUX solo es del lado del servidor, para sustituir a ajax en el cliente con Angular por ejemplo se tendria que usar objetos Reactive?
Si claro webflux es lado servidor o aplicacion servidor cliente con spring mvc
Primero agradecer el articulo. Y una recomendación:
Aqui se habla de flux y se incluye Reactive Web y Angular o React sin mencionar que relacion tiene con Spring WebFlux. No se si esto es obligatorio, muy dependiente, auxiliar o prescindible.
Yo por ejempo no he usado ni conozco Angular, React pero si spring mvc y este articulo de WebFlux no me aclara nada sobre este punto que comento.
Me apunto la idea 🙂 , Angular tambien soporta programacion reactiva 🙂
Y que tiene que ver la programacino reactiva con WebFlex? Y con Angular?
Ese es el punto que quiero indicar. Si tiene relación, mucha, se deberia mencionar y si no la tiene mejor no incluir variables externas que dificulten la comprensión.
Aun así gracias. Sigo este blog con mucho interes.
Ambos son Frameworks Reactivos 😉 . Intentaré escribir algo más del tema
Deberías hacer un curso de sobre spring web flux utilizando angular para la construcción de un app reactiva. 🙂
Me apunto la idea 🙂