Programación reactiva con Spring 5 WebFlux

Spring WebFlux es la respuesta de Spring al creciente problema del bloqueo de la arquitectura de E/S y permite a los desarrolladores crear aplicaciones reactivas de alto rendimiento en Spring en un entorno familiar.

Introducción

Primavera WebFlux es la respuesta de Spring a el problema creciente del bloqueo de la arquitectura de E/S.

A medida que los datos se vuelven cada vez más cruciales en nuestra era, los enfoques que adoptamos para recuperarlos y manipularlos cambian. Convencionalmente, la mayoría de los enfoques eran de "bloqueo", o más bien, sincrónicos. Lo que esto significa es que el acceso a un recurso impidió que la aplicación accediera/procesara otro hasta que se resolvió el recurso anterior.

Esto estaba perfectamente bien con una cantidad limitada de datos y recursos, aunque con la creciente demanda de datos a través de aplicaciones de alto rendimiento, esto se convirtió en un gran problema.

Los editores comenzaron a abrumar a los suscriptores y manejar los recursos uno por uno, al igual que tener un solo empleado trabajando en todo el supermercado, se volvió demasiado lento para una experiencia de usuario fluida.

La solución es obvia: tener más empleados que atiendan a los clientes. En términos de aplicaciones de software, esto significa un entorno de subprocesos múltiples y llamadas asincrónicas, sin bloqueo.

Pila de resorte reactivo

La popular pila de servlets de Spring, que comprende Spring MVC, utiliza los métodos convencionales de acceso y procesamiento de datos como llamadas sincrónicas. Con la introducción de Spring 5, la pila reactiva de Spring se construyó sobre Núcleo del reactor.

La pila reactiva de Spring ofrece soporte adicional para contenedores Neto y Servlet 3.1+, lo que brinda un mayor rendimiento para las aplicaciones reactivas:

spring webflux reactor
[Crédito Primavera]{.small}

Primavera WebFlux

Spring WebFlux es un módulo equivalente a Spring MVC. Donde Spring MVC implementa E/S de bloqueo síncrono, Spring WebFlux implementa la programación reactiva a través de [Corrientes reactivas] (https://www.reactive-streams.org).

Por lo general, usará uno u otro, aunque también puede combinarlos.

Dependencias de Spring WebFlux

Con un proyecto simple de Spring Boot inicializado a través de Spring Initializr, es suficiente agregar una sola dependencia:

1
2
3
4
5
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
    <version>{version}</version>
</dependency>

spring-boot-starter-webflux incluye spring-web, spring-webflux, spring-boot-starter, spring-boot-starter-reactor-netty, etc., así que hay No es necesario que esté presente ninguna otra dependencia.

Para las aplicaciones que usan Gradle para la administración de dependencias, se puede agregar Spring WebFlux en el archivo build.gradle de la aplicación:

1
compile group: 'org.springframework.boot', name: 'spring-boot-starter-webflux', version: '2.2.2.RELEASE'

Mono y Flux

En Spring WebFlux, los datos devueltos por cualquier operación se empaquetan en un flujo reactivo. Hay dos tipos que encarnan este enfoque y son los componentes básicos de las aplicaciones WebFlux: Mono y Flux.

Mono es un flujo que devuelve cero elementos o un solo elemento (0..1), mientras que Flux es un flujo que devuelve cero o más elementos (0..N).

Por lo tanto, Mono se usa cuando espera un resultado único (o ninguno), como recuperar un usuario único de la base de datos, mientras que Flux se usa cuando espera varios resultados o una colección de algún tipo. .

Controlador Spring WebFlux

De manera similar a cómo usamos los controladores en Spring MVC clásico, para la creación de API REST asíncronas, usamos el controlador WebFlux. Incluso las convenciones de nomenclatura son similares para garantizar una transición fácil entre estos dos enfoques.

Para marcar una clase como controlador, usamos la anotación @RestController en un nivel de clase.

Tener Spring WebFlux y las dependencias de Reactor Core en la ruta de clase le permitirá a Spring saber que @RestController es de hecho un componente reactivo y agregará soporte para Mono y Flux.

Configuración Spring WebFlux

Como es estándar con Spring Boot, manejaremos la configuración a través de anotaciones. Las anotaciones @Configuration y @EnableWebFlux marcan una clase como una clase de configuración y la administración de beans de Spring la registrará:

1
2
3
@Configuration
@EnableWebFlux 
public class WebFluxConfig {}

Para usar o ampliar la API de configuración de WebFlux existente, puede ampliar la interfaz WebFluxConfigurer:

1
2
3
@Configuration
@EnableWebFlux 
public class WebFluxConfig implements WebFluxConfigurer {}

CORS con Spring WebFlux

WebFlux también ofrece compatibilidad con CORS (Cross-Origin Resource Sharing), muy similar a la pila de servlet Spring MVC. La configuración de CORS se puede establecer a nivel de proyecto, así como a nivel de controlador y método de controlador.

Para agregar la configuración de CORS a nivel de proyecto, debe @Overrride el método addCorsMappings() desde la interfaz WebFluxConfigurer:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Configuration
@EnableWebFlux 
public class WebFluxConfig implements WebFluxConfigurer { 
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("https://wikihtp.com")
            .allowedMethods("GET", "PUT", "DELETE")
            .allowedHeaders("testHeader")
            .allowCredentials(true);
    }
}

Y para agregar la configuración de CORS a un nivel más granular, se usa la anotación @CrossOrigin. Esto permite especificar los detalles de CORS a nivel de controlador y método.

Cuando @CrossOrigin se usa en el nivel de clase del controlador, todas las configuraciones CORS de nivel de método heredan de la configuración de nivel de clase:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@CrossOrigin(origins = "https://wikihtp.com")
@RestController
@RequestMapping("/resource") 
public class ResourceController {

    @GetMapping("/{id}")
    public Mono<Resource> getResource(@PathVariable String id) {

    }
}

Seguridad con Spring Webflux

Spring también ofrece las opciones de seguridad estándar para su marco WebFlux. Debe agregar @EnableWebFluxSecurity en el nivel de clase para habilitar las configuraciones de seguridad en el proyecto:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@EnableWebFluxSecurity
public class WebfluxSecurity {

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(
      ServerHttpSecurity http) {
        http.csrf().disable()
          .authorizeExchange()
          .pathMatchers(HttpMethod.GET, "/resource/").hasRole("ADMIN")
          .pathMatchers("/**").permitAll()
          .and()
          .httpBasic();
        return http.build();
    }
}

Cliente web Spring WebFlux

Spring WebFlux también incluye un cliente web reactivo para administrar llamadas REST. Reactor-Netty se usa de manera predeterminada para comunicarse con el servidor WebFlux.

El objeto de cliente de WebFlux se puede crear utilizando métodos de fábrica estáticos o a través de su método de creación (más personalización).

Los métodos de fábrica estáticos son WebClient.create() y WebClient.create(String baseUrl). Por otro lado, WebClient.builder ofrece las siguientes opciones para agregar más detalles al objeto del cliente web:

  • uriBuilderFactory
  • Cabecera por defecto
  • cookie por defecto
  • Solicitud por defecto
  • filtro
  • Estrategias de intercambio
  • ConectorCliente

Echaremos un vistazo más de cerca a esto en la sección anterior donde se crea una aplicación de demostración.

Aplicación de demostración

Crearemos una API REST reactiva simple utilizando los componentes estándar de WebFlux, que actuará como un servidor reactivo. Luego, se construirá una aplicación cliente web reactiva, que recupera información de este servidor y procesa los datos.

Servidor web WebFlux

Repositorio

Comencemos sentando las bases para una aplicación reactiva mediante la creación de una interfaz de repositorio reactiva.

1
public interface ResourceRepository extends ReactiveCrudRepository<Resource, String> {}

Hemos ampliado nuestro repositorio desde ReactiveCrudRepository de WebFlux, que devolverá datos como tipos de datos Mono o Flux, dependiendo de la cantidad de elementos que se puedan recuperar.

Para usar MongoDB con Spring WebFlux, agregaremos una clase de configuración que le dice a Spring que la base de datos debe manejarse como un componente reactivo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@EnableReactiveMongoRepositories
public class MongoDbConfiguration extends AbstractReactiveMongoConfiguration {

    @Override
    public MongoClient reactiveMongoClient() {
        return MongoClients.create();
    }

    @Override
    protected String getDatabaseName() {
        return "testDatabase";
    }
}

Controlador

Con nuestra capa de datos terminada y configurada, creemos un controlador REST simple que recuperará los recursos Mono y Flux a través de solicitudes GET:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@RestController
@RequestMapping("/resource")
public class ResourceController {

    @Autowired
    ResourceRepository resourceRepository;

    @GetMapping("/{id}")
    public Mono<Resource> getResource(@PathVariable String id) {
        return resourceRepository.findById(id);
    }

    @GetMapping
    public Flux<Resource> getResources() {
        return resourceRepository.findAll();
    }
}

Aquí estamos usando nuestro ResourceRepository reactivo para encontrar un recurso usando el id que viene de la solicitud. Dado que estamos buscando una ID única, el resultado esperado es 1 registro (se encuentra el recurso con la ID pasada) o 0 registros (no hay registros en la base de datos), por lo tanto, Mono se usa como un tipo de retorno.

Dado que findAll() puede devolver más de un elemento de recurso (si está presente), se usa Flux como tipo de retorno.

Cliente web WebFlux

Ahora que tenemos una aplicación REST básica configurada, creemos un cliente Spring WebFlux que puede enviar solicitudes a una aplicación REST de WebFlux.

Para iniciar un cliente web, necesitamos crear un objeto WebClient usando la URL del servidor:

1
2
3
4
public WebClient openConnection(String url) {
    client = WebClient.create(url);
    return client;
}

Una vez que se crea el objeto WebClient, podemos usarlo para realizar solicitudes al servidor web.

Hagamos una solicitud al servidor, recuperando un recurso con la ID dada:

1
2
3
4
5
6
7
8
public void getResourceById(String id) {
    Mono<Resource> result = client.get()
            .uri("/resource/{id}", "1")
            .retrieve()
            .bodyToMono(Resource.class);

    result.subscribe(System.out::println);
}

El método bodyToMono() es responsable de empaquetar el cuerpo de la respuesta en un Mono.

De manera similar, si el punto final que llama devuelve datos como Flux, podemos recuperarlos usando el siguiente método:

1
2
3
4
5
6
7
8
public void getAllResources() {
    Flux<Resource> result = client.get()
            .uri("/resource")
            .retrieve()
            .bodyToFlux(Resource.class);

    result.subscribe(System.out::println);
}

Conclusión

Spring Framework permite a los desarrolladores crear aplicaciones y API reactivas y sin bloqueo utilizando la pila WebFlux de Spring. WebFlux ofrece anotaciones muy similares a las que se utilizan en las aplicaciones Spring MVC clásicas, lo que facilita a los desarrolladores la transición al código reactivo.

En esta guía, hemos repasado los conceptos más importantes del marco WebFlux y hemos creado una aplicación de demostración para mostrarlos en la práctica.

El código fuente se puede encontrar en GitHub. x).