Hands-On Cloud:Native Microservices with Jakarta EE
上QQ阅读APP看书,第一时间看更新

Spring WebFlux and reactive stacks

We can consider RxJava as a low-level framework, containing specific implementations of reactive concepts. But how do architectural frameworks, such as Spring, think about reactive structure in enterprise environments?

The traditional web framework developed in Spring Suite, Spring Web MVC, was built on top of the Servlet API and servlet containers, mainly Tomcat. The reactive implementation, named Spring WebFlux, was introduced in Spring 5.0. It fully supports Reactive Streams, non-blocking, and runs on different server types:

  •  Traditional, such as a Servlet 3.1+ container (Tomcat)
  •  A new performant web server based on Java NIO, such as Undertow
  •  Asynchronous event-driven network servers, such as Netty

Historically, Spring has represented the framework that allowed us to overcome the limits present in the Java EE platform. As described in Chapter 1, Jakarta EE – the New Open Source Life of JEE, it is not appropriate to say that Spring has represented the evolutionary push that has allowed for continuous and constant improvement of the Java EE ecosystem.

Also, in the case of reactive architectures, Spring presented solutions that are capable of implementing what was requested by the Reactive Streams and not easily obtained with previous versions of Java EE.

In reactive architecture, but also in cloud environments and microservice systems, there is a need for non-blocking APIs to handle high concurrency of requests with a small number of threads to easily scale the infrastructure with fewer hardware resources.

Java EE 7 and Servlet 3.1 did provide an API for non-blocking I/O, but some of the core APIs are synchronous, such as Filter, or blocking, such as those for the getParameter and getPart methods of the Servlet API.

This was the reason that drove Spring to build a new common API that could be used inside any non-blocking runtime. Netty based its core around these concepts and, for that reason, was chosen as the runtime execution environment for Spring WebFlux.

Spring WebFlux is the new Spring implementation of Reactive Stream's specifications. The main target is to create a new set of functional and richer APIs that can enable developers to compose async logic, which is the basis of reactive programming. The core library of Spring WebFlux is Reactor (https://github.com/reactor/reactor); it provides two main APIs: Mono and Flux. Thanks to these new APIs, it is easy to work on data sequences of 0..1 and 0..n through a rich set of methods and implementations.

So to build a reactive stack in Spring, should I leave Spring MVC and use only Spring WebFlux? 

The answer is no. The two modules can coexist and are designed for consistency with each other. In particular, in an MSA, you are free to use a polyglot microservice, either in terms of programming languages or in features implemented by the chosen framework.

In the case of Spring, you can use Spring MVC, Spring WebFlux controllers, or, with Spring WebFlux, functional endpoints. The difference is strictly related to the programming model, which could be annotated controllers or functional endpoints.

Let's try to realize a simple example of exposure of a service via an HTTP protocol through Spring WebFlux.

We can use Spring Initializr to quickly bootstrap our application. You should connect to https://start.spring.io/ and then perform the following steps:

  1.  Set com.microservice.webflux as the project's Group.
  2.  Set demo as the Artifact name.
  3.  Set reactor in the Search for dependencies field and select Reactive Web from the drop-down menu.
  4.  Click the Generate Project button to download a scaffold of a Maven project.

This is a snapshot of what you have done so far:

Once downloaded, unzip the project's file into a directory and open your Maven project with your favorite IDE to create the classes that are needed to build the service.

Please ensure you have set the Spring repositories into the Maven settings.xml file. Otherwise, you need to do so by choosing from the following, based on your preference:

The first class to create is the handler that will be responsible for handling the requests and creating the responses:

package com.microservice.webflux.demo;
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 DemoHandler {
public Mono<ServerResponse> hello(ServerRequest request) {
return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
.body(BodyInserters.fromObject("Welcome on Spring WebFlux world!"));
}
}

This handler class has a static behavior and always returns the string "Welcome on Spring WebFlux world!". In a real scenario, it could return a stream of items, derived from a database query or that were generated by calculations. The handler method returns a Mono object, the Reactive equivalent of Java 8's CompletableFuture, which holds a ServerResponse body.

After creating the handler, we need to build a router class that intercepts the requests directed to a specific path, in our case /welcome, and returns as a response the result of the invocation of the handler method:

package com.microservice.webflux.demo;

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 DemoRouter {

@Bean
public RouterFunction<ServerResponse> route(DemoHandler demoHandler) {
return RouterFunctions.route(RequestPredicates.GET("/welcome")
.and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), demoHandler::welcome);
}
}

Now that we have the handler and the router, it's time to construct the WebClient class, which is needed to hold the content of the invocation and exchange it into a specific return value type, in our case, a string:

package com.microservice.webflux.demo;

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 DemoWebClient {

// Externalize the URL in a configuration file
private final WebClient client = WebClient.create("http://localhost:8080");
private final Mono<ClientResponse> result =
client.get().uri("/welcome").accept(MediaType.TEXT_PLAIN)
.exchange();

public String getResult() {
return ">> result = " + result.flatMap(res -> res.bodyToMono(String.class)).block();
}
}

This is a classic example where Spring WebFlux introduces an important change compared to the traditional Spring MVC RESTTemplate. The WebClient implementation of Spring MVC is blocking while Spring WebFlux is non-blocking, following the reactive philosophy.

Finally, we need to make our simple application executable. Reactive Spring WebFlux supports an embedding Netty server as the HTTP runtime that will host the application and expose the service via the HTTP protocol:

package com.microservice.webflux.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplicationpublic class DemoApplication {

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
DemoWebClient demoWebClient = new DemoWebClient();
System.out.println(demoWebClient.getResult());
}
}

The main() method of my class uses Spring Boot's SpringApplication.run() method to start the application. Now, compile the application using Maven, via the mvn clean package command, or through your favorite IDE, in order to obtain the executable JAR file that will be created inside the $PROJECT_HOME/target directory that is the location where you unzipped the scaffold project that was created by Spring Initializr.

Finally, it's time to run our application via the java -jar target/demo-0.0.1-SNAPSHOT.jar command (ensure you are in the $PROJECT_HOME directory).

You will obtain an output like this:

As you will notice, at the end of the output there is the string, Welcome on Spring WebFlux world!, that is the result of the method defined in the DemoHandler class. You can also test the method invocation using a browser and setting the URL to http://localhost:8080/welcome. You will obtain the same result:

If you prefer to follow test-driven development (TDD), you can build your JUnit test to verify that the service's output is what you expected:

package com.microservice.webflux.demo;

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 DemoRouterTests {

@Autowired
private WebTestClient webTestClient;

@Test
public void testWelcome() {
webTestClient.get().uri("/welcome").accept(MediaType.TEXT_PLAIN)
.exchange().expectStatus().isOk()
.expectBody(String.class).isEqualTo("Welcome on Spring WebFlux world!");
}
}

By launching the mvn test command, you will be able to verify that the output is what you expected.

In sum, we just implemented an easy scenario that shows you how you can implement, expose, and consume an API in a reactive way using Spring Reactor and Spring WebFlux.