Spring WebFlux – Programación Reactiva con Spring

Spring WebFlux es el nuevo módulo introducido en Spring 5. Spring WebFlux es el primer paso hacia el modelo de programación reactiva en el framework de Spring.

Programación Reactiva en Spring

Si eres nuevo en el modelo de programación reactiva, te sugeriría encarecidamente que leas los siguientes artículos para aprender sobre la programación reactiva.

Si eres nuevo en Spring 5, por favor revisa Características de Spring 5.

Spring WebFlux

Spring WebFlux es la alternativa al módulo Spring MVC. Spring WebFlux se utiliza para crear aplicaciones totalmente asíncronas y no bloqueantes construidas sobre el modelo de ejecución de bucle de eventos. El siguiente diagrama de la Documentación Oficial de Spring proporciona una gran visión comparativa de Spring WebFlux con Spring Web MVC. Si estás buscando desarrollar una aplicación web o un servicio web Rest en un modelo reactivo no bloqueante, entonces puedes considerar Spring WebFlux. Spring WebFlux es compatible con Tomcat, Jetty, contenedores Servlet 3.1+ y también en entornos de ejecución no Servlet como Netty y Undertow. Spring WebFlux está construido sobre Project Reactor. Project Reactor es la implementación de la especificación Reactive Streams. Reactor proporciona dos tipos:

  1. Mono: implementa Publisher y devuelve 0 o 1 elementos
  2. Flux: implementa Publisher y devuelve N elementos.

Ejemplo de Hola Mundo de Spring WebFlux

Vamos a construir una aplicación simple “Hello World” de Spring WebFlux. Crearemos un servicio web rest simple y usaremos Spring Boot para ejecutarlo en el servidor Netty por defecto. La estructura final de nuestro proyecto se ve como en la imagen a continuación. Vamos a analizar cada componente de la aplicación uno por uno.

Dependencias Maven de Spring WebFlux

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.journaldev.spring</groupId>
  <artifactId>SpringWebflux</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>Spring WebFlux</name>
  <description>Spring WebFlux Example</description>
  
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <jdk.version>1.9</jdk.version>
    </properties>
    
  <parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.1.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<dependencies>
		<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>
		</dependency>

		<dependency>
			<groupId>io.projectreactor</groupId>
			<artifactId>reactor-test</artifactId>
			<scope>test</scope>
		</dependency>
    </dependencies>
	<repositories>
		<repository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>spring-snapshots</id>
			<name>Spring Snapshots</name>
			<url>https://repo.spring.io/snapshot</url>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</pluginRepository>
		<pluginRepository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</pluginRepository>
	</pluginRepositories>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.7.0</version>
                    <configuration>
                        <source>${jdk.version}</source>
                        <target>${jdk.version}</target>
                    </configuration>
                </plugin>
            </plugins>
    </pluginManagement>
    </build>
    
</project>

Las dependencias más importantes son spring-boot-starter-webflux y spring-boot-starter-parent. Algunas otras dependencias son para crear casos de prueba JUnit.

Manejador de Spring WebFlux

El método del manejador de Spring WebFlux maneja la petición y devuelve un Mono o un Flux como respuesta.

package com.journaldev.spring.component;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;

import reactor.core.publisher.Mono;

@Component
public class HelloWorldHandler {

	public Mono<ServerResponse> helloWorld(ServerRequest request) {
		return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
			.body(BodyInserters.fromObject("Hello World!"));
	}
}

Observa que el componente reactivo Mono contiene el cuerpo de ServerResponse. También observa la cadena de funciones para establecer el tipo de contenido de retorno, el código de respuesta y el cuerpo.

Enrutador de Spring WebFlux

Los métodos del enrutador se utilizan para definir rutas para la aplicación. Estos métodos devuelven un objeto RouterFunction que también contiene el cuerpo de la respuesta del ServerResponse.

package com.journaldev.spring.component;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;

@Configuration
public class HelloWorldRouter {

	@Bean
	public RouterFunction<ServerResponse> routeHelloWorld(HelloWorldHandler helloWorldHandler) {

		return RouterFunctions.route(RequestPredicates.GET("/helloWorld")
                .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), helloWorldHandler::helloWorld);
	}
}

Entonces estamos exponiendo un método GET para /helloWorld y la llamada del cliente debería aceptar una respuesta de texto plano.

Aplicación Spring Boot

Vamos a configurar nuestra sencilla aplicación WebFlux con Spring Boot.

package com.journaldev.spring;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

Si observas el código anterior, no hay nada relacionado con Spring WebFlux. Pero Spring Boot configurará nuestra aplicación como Spring WebFlux ya que hemos añadido la dependencia del módulo spring-boot-starter-webflux.

Soporte de Módulos Java 9

Nuestra aplicación está lista para ejecutarse en Java 8, pero si estás usando Java 9 entonces también necesitamos agregar la clase module-info.java.

module com.journaldev.spring {
    requires reactor.core;
    requires spring.web;
    requires spring.beans;
    requires spring.context;
    requires spring.webflux;
    requires spring.boot;
    requires spring.boot.autoconfigure;
    exports com.journaldev.spring;
}

Ejecutando la Aplicación Spring WebFlux Spring Boot

Si tienes soporte para Spring en Eclipse, entonces puedes ejecutar la clase anterior como una aplicación de Spring Boot. Si prefieres usar la línea de comandos, abre la terminal y ejecuta el comando mvn spring-boot:run desde el directorio fuente del proyecto. Una vez que la aplicación esté en ejecución, observa los siguientes mensajes de registro para asegurarte de que todo está bien con nuestra aplicación. También es útil cuando amplíes esta aplicación simple agregando más rutas y funcionalidades.

2018-05-07 15:01:47.893  INFO 25158 --- [           main] o.s.w.r.f.s.s.RouterFunctionMapping      : Mapped ((GET && /helloWorld) && Accept: [text/plain]) -> com.journaldev.spring.component.HelloWorldRouter$$Lambda$501/704766954@6eeb5d56
2018-05-07 15:01:48.495  INFO 25158 --- [ctor-http-nio-1] r.ipc.netty.tcp.BlockingNettyContext     : Started HttpServer on /0:0:0:0:0:0:0:0:8080
2018-05-07 15:01:48.495  INFO 25158 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
2018-05-07 15:01:48.501  INFO 25158 --- [           main] com.journaldev.spring.Application        : Started Application in 1.86 seconds (JVM running for 5.542)

Es claro a partir de los registros que nuestra aplicación se está ejecutando en el servidor Netty en el puerto 8080. Sigamos y probemos nuestra aplicación.

Prueba de la aplicación Spring WebFlux

Podemos probar nuestra aplicación con varios métodos.

  1. Usando el comando CURL

    $ curl https://localhost:8080/helloWorld
    ¡Hola Mundo!
    $ 
    
  2. Lanzar URL en el navegador

  3. Usando WebTestClient de Spring 5 Aquí tienes un programa de prueba JUnit para probar nuestro servicio web Rest utilizando WebTestClient de Spring 5 web reactiva.

    package com.journaldev.spring;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.http.MediaType;
    import org.springframework.test.context.junit4.SpringRunner;
    import org.springframework.test.web.reactive.server.WebTestClient;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class SpringWebFluxTest {
    
    	@Autowired
    	private WebTestClient webTestClient;
    
    	
    	@Test
    	public void testHelloWorld() {
    		webTestClient
    		.get().uri("/helloWorld") // Método GET y URI
    		.accept(MediaType.TEXT_PLAIN) // Configurando ACCEPT-Content
    		.exchange() // Da acceso a la respuesta
    		.expectStatus().isOk() // Verificando si la respuesta es OK
    		.expectBody(String.class).isEqualTo("¡Hola Mundo!"); // Verificando el tipo de respuesta y el mensaje
    	}
    
    }
    

    Ejecútalo como un caso de prueba JUnit y debería pasar sin problemas.

  4. Usando WebClient de Spring Web Reactive También podemos usar WebClient para llamar al servicio web REST.

    package com.journaldev.spring.client;
    
    import org.springframework.http.MediaType;
    import org.springframework.web.reactive.function.client.ClientResponse;
    import org.springframework.web.reactive.function.client.WebClient;
    
    import reactor.core.publisher.Mono;
    
    public class HelloWorldWebClient {
    
        public static void main(String args[]) {
            WebClient client = WebClient.create("https://localhost:8080");
    
            Mono<ClientResponse> result = client.get()
                .uri("/helloWorld")
                .accept(MediaType.TEXT_PLAIN)
                .exchange();
    
            System.out.println("Resultado = " + result.flatMap(res -> res.bodyToMono(String.class)).block());
        }
        
    }
    

    Solo ejecútalo como una aplicación Java simple y deberías ver la salida adecuada con muchos mensajes de depuración.

Resumen

En esta publicación, aprendimos sobre Spring WebFlux y cómo construir un servicio web Reactivo de tipo “hello world”. Es bueno ver que marcos populares como Spring apoyan el modelo de programación reactivo. Pero tenemos mucho que cubrir, porque si todas tus dependencias no son reactivas y no bloqueantes, entonces tu aplicación tampoco es verdaderamente reactiva. Por ejemplo, los proveedores de bases de datos relacionales no tienen controladores reactivos porque dependen de JDBC, que no es reactivo. Por lo tanto, la API de Hibernate también es no reactiva. Entonces, si estás utilizando bases de datos relacionales, aún no puedes construir una aplicación verdaderamente reactiva. Estoy esperanzado de que esto cambie antes que tarde.

Puedes descargar el código del proyecto desde mi Repositorio en GitHub.

Referencia: Documentación Oficial

Source:
https://www.digitalocean.com/community/tutorials/spring-webflux-reactive-programming