Spring WebFlux – Programação Reativa Spring

Spring WebFlux é o novo módulo introduzido no Spring 5. Spring WebFlux é o primeiro passo em direção ao modelo de programação reativa no framework Spring.

Programação Reativa Spring

Se você é novo no modelo de programação reativa, então eu sugeriria fortemente que você leia os seguintes artigos para aprender sobre programação reativa.

Se você é novo no Spring 5, por favor, leia Recursos do Spring 5.

Spring WebFlux

Spring WebFlux é a alternativa ao módulo Spring MVC. Spring WebFlux é usado para criar aplicações totalmente assíncronas e não bloqueantes, construídas no modelo de execução de loop de eventos. O diagrama abaixo, da Documentação Oficial do Spring, fornece uma ótima visão sobre a comparação entre o Spring WebFlux e o Spring Web MVC. Se você está procurando desenvolver uma aplicação web ou um serviço web Rest em um modelo reativo não bloqueante, então você pode olhar para o Spring WebFlux. O Spring WebFlux é suportado no Tomcat, Jetty, contêineres Servlet 3.1+ e também em tempo de execução não Servlet, como o Netty e o Undertow. O Spring WebFlux é construído no Project Reactor. O Project Reactor é a implementação da especificação Reactive Streams. O Reactor fornece dois tipos:

  1. Mono: implementa Publisher e retorna 0 ou 1 elementos
  2. Flux: implementa Publisher e retorna N elementos.

Exemplo de “Hello World” do Spring WebFlux

Vamos construir uma aplicação simples de “Hello World” com Spring WebFlux. Vamos criar um serviço web rest simples e usar o Spring Boot para executá-lo no servidor Netty padrão. A estrutura final do nosso projeto parece com a imagem abaixo. Vamos examinar cada componente da aplicação um por um.

Dependências do Maven do 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>

As dependências mais importantes são spring-boot-starter-webflux e spring-boot-starter-parent. Algumas outras dependências são para criar casos de teste JUnit.

Manipulador do Spring WebFlux

O método Manipulador do Spring WebFlux lida com a solicitação e retorna Mono ou Flux como resposta.

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!"));
	}
}

Observe que o componente reativo Mono mantém o corpo do ServerResponse. Também observe a cadeia de funções para definir o tipo de conteúdo de retorno, código de resposta e corpo.

Roteador do Spring WebFlux

Os métodos do roteador são usados para definir rotas para a aplicação. Esses métodos retornam o objeto RouterFunction, que também contém o corpo de 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);
	}
}

Portanto, estamos expondo um método GET para /helloWorld e a chamada do cliente deve aceitar uma resposta de texto simples.

Aplicação Spring Boot

Vamos configurar nossa simples aplicação WebFlux com o 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);
	}
}

Se você olhar para o código acima, não há nada relacionado ao Spring WebFlux. Mas o Spring Boot irá configurar nossa aplicação como Spring WebFlux, pois adicionamos a dependência do módulo spring-boot-starter-webflux.

Suporte a Módulos Java 9

Nossa aplicação está pronta para ser executada no Java 8, mas se estiver usando o Java 9, também precisamos adicionar a classe 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;
}

Executando a Aplicação Spring WebFlux Spring Boot

Se você tiver suporte ao Spring no Eclipse, poderá executar a classe acima como um aplicativo Spring Boot. Se preferir usar a linha de comando, abra o terminal e execute o comando mvn spring-boot:run a partir do diretório de origem do projeto. Quando o aplicativo estiver em execução, observe as mensagens de log a seguir para garantir que tudo esteja correto com nosso aplicativo. Isso também é útil ao expandir este aplicativo simples, adicionando mais rotas e 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)

Está claro nos logs que nosso aplicativo está sendo executado no servidor Netty na porta 8080. Vamos em frente e testar nossa aplicação.

Teste do Aplicativo Spring WebFlux

Podemos testar nosso aplicativo com vários métodos.

  1. Usando o comando CURL

    $ curl https://localhost:8080/helloWorld
    Hello World!
    $ 
    
  2. Iniciar URL no Navegador

  3. Usando WebTestClient do Spring 5 Aqui está um programa de teste JUnit para testar nosso serviço da web Rest usando WebTestClient do Spring 5 web reativa.

    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 e URI
    		.accept(MediaType.TEXT_PLAIN) // definindo ACCEPT-Content
    		.exchange() // dá acesso à resposta
    		.expectStatus().isOk() // verificando se a resposta está OK
    		.expectBody(String.class).isEqualTo("Hello World!"); // verificando o tipo de resposta e a mensagem
    	}
    
    }
    

    Execute como um caso de teste JUnit e deverá passar com cores brilhantes.

  4. Usando WebClient do Spring Web Reactive Também podemos usar WebClient para chamar o serviço web REST.

    pacote 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());
    	}
    	
    }
    

    Basta executá-lo como uma aplicação Java simples e você deverá ver a saída adequada com muitas mensagens de depuração.

Resumo

Neste post, aprendemos sobre o Spring WebFlux e como construir um serviço web reativo de exemplo. É bom ver que frameworks populares como o Spring estão apoiando o modelo de programação reativa. Mas temos muito a cobrir, porque se todas as suas dependências não forem reativas e não bloqueadoras, então sua aplicação também não será verdadeiramente reativa. Por exemplo, os fornecedores de banco de dados relacionais não têm drivers reativos porque dependem do JDBC, que não é reativo. Portanto, a API do Hibernate também não é reativa. Então, se você estiver usando bancos de dados relacionais, não poderá construir uma aplicação verdadeiramente reativa ainda. Estou esperançoso de que isso mudará mais cedo do que tarde.

Você pode baixar o código do projeto do meu Repositório GitHub.

Referência: Documentação Oficial

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