Responsive programming practice (08)-WebFlux, using annotation programming mode to build asynchronous non-blocking services

1 Introduction

The background and significance of the birth of WebFlux components in the Spring family are clarified. As a new type of Web service development component:

  • Fully considering the compatibility with the original Spring MVC development model, developers can still use annotation-based programming to create responsive Web services
  • WebFlux also introduces a new development model based on functional programming

Focus first on the annotation-based programming model.

2 Introducing Spring WebFlux

If you are creating a WebFlux application for the first time, it is easiest to use the Spring Initializer initialization template provided by Spring. Directly visit the Spring Initializer website ( http://start.spring.io/ ), choose to create a Maven project and specify the corresponding configuration items, and then select Spring Reactive Web in the added dependencies, we can get a runnable WebFlux template project.

Drawing 0.png

A diagram of a responsive web application initialized with Spring Initializer.

pom file in the template project

<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>
  • The core is spring-boot-starter-webflux, which forms the basis for responsive web application development
  • spring-boot-starter-test is a test component library including JUnit, Spring Boot Test, Mockit and other common test tool classes
  • reactor-test is a test component library used to test the Reactor framework

Of course, you can also create an arbitrary Maven project and add these dependencies. In this way, the initialization environment for building reactive web services with Spring WebFlux is ready.

3 Create responsive RESTful services using the annotation programming model

  • Based on Java annotations, this programming model is consistent with traditional Spring MVC
  • Use a functional programming model

First introduce the first implementation.

3.1 RESTful service and traditional creation method

Before creating a responsive Web service, let's review the traditional RESTful service creation method.

REST (Representational State Transfer) is essentially an architectural style rather than a specification. This architectural style regards the access portal located on the server side as a resource, and each resource uses a URI to represent a unique access address. In the request process, standard HTTP methods are used, such as GET, PUT, POST, and DELETE.

Use Spring Boot to build a traditional RESTful service and create a Bootstrap startup class. The Bootstrap class structure is simple and relatively solid:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class HelloApplication {
    
    
 
    public static void main(String[] args) {
    
    
        SpringApplication.run(HelloApplication.class, args);
    }
}

The class annotated with @SpringBootApplication is the entry class of the entire application, which is equivalent to starting the Spring container. Once the Spring container has started, we can build HTTP endpoints by providing a series of Controller classes, the simplest of which is shown below.

@RestController
public class HelloController {
    
    
 
    @GetMapping("/")
    public String hello() {
    
    
        return "Hello World!";
    }
}

The @RestController annotation inherits from the @Controller annotation in Spring MVC. Compared with the traditional @Controller annotation, the @RestController annotation has a built-in JSON-based serialization/deserialization method, which is specially used to build lightweight RESTful endpoints. With this feature, we can use the @RestController annotation instead of the @Controller annotation to simplify development when building RESTful services.

The @GetMapping annotation is also similar to the @RequestMapping annotation in Spring MVC. Spring Boot 2 introduces a batch of new annotations, such as @PutMapping, @PostMapping, @DeleteMapping, etc., to facilitate developers to explicitly specify HTTP request methods. Of course, you can also continue to use the original @RequestMapping to achieve the same effect.

Typical Controller

Implemented an HTTP endpoint to obtain order information according to the order number OrderNumber. The access URI of this endpoint is "orders/{orderNumber}", which is composed of root path "orders" + subpath "/{orderNumber}", and also specifies the corresponding HTTP request method and the required parameters:

@RestController
@RequestMapping(value="orders")
public class OrderController {
    
    
  
    @Autowired
    private OrderService orderService;       
    
    @GetMapping(value = "/{orderNumber}")
    public Order getOrderByOrderNumber(@PathVariable String orderNumber) {
    
       
        Order order = orderService.getOrderByOrderNumber(orderNumber);
        
        return order;
    }
}

Creating a responsive RESTful service based on the annotation programming model is very similar to using traditional Spring MVC. By mastering the basic concepts and skills of responsive programming, there is almost no learning cost for using this programming model in WebFlux applications.

3.2 Building responsive RESTful services through annotations

For the two RESTful service examples introduced earlier, show how to give their reactive versions with respect to the reactive programming model.

The first responsive RESTful service comes from the responsive transformation of the original HelloController example,

After transformation:

@RestController
public class HelloController {
    
    
 
    @GetMapping("/")
    public Mono<String> hello() {
    
    
        return Mono.just("Hello World!");
    }
}

The return value of the hello() method is converted from a normal String object to a Mono object.

The difference between Spring WebFlux and Spring MVC is that the former uses the Flux and Mono objects provided in Reactor instead of POJO.

The first reactive RESTful service is very simple, and in what follows, we'll go one step further and build a reactive RESTful service with a Service layer implementation. The Service layer generally uses a specific data access layer to implement data operations, but because responsive data access is an independent topic, I will discuss it in the follow-up "14 | Responsive full stack: Responsive programming can provide data access What kind of changes does the process bring about?" to expand.

In this talk, we still try to shield the complexity brought about by responsive data access. The data layer implements the Service layer component by means of stubs. We will build a stub service StubOrderService for common order services, as shown below.

@Service
public class StubOrderService {
    
    
 
    private final Map<String, Order> orders = new ConcurrentHashMap<>();
 
    public Flux<Order> getOrders() {
    
    
        return Flux.fromIterable(this.orders.values());
    }
 
    public Flux<Order> getOrdersByIds(final Flux<String> ids) {
    
    
        return ids.flatMap(id -> Mono.justOrEmpty(this.orders.get(id)));
    }
 
    public Mono<Order> getOrdersById(final String id) {
    
    
        return Mono.justOrEmpty(this.orders.get(id));
    }
 
    public Mono<Void> createOrUpdateOrder(final Mono<Order> productMono) {
    
    
        return productMono.doOnNext(product -> {
    
    
            orders.put(product.getId(), product);
        }).thenEmpty(Mono.empty());
    }
 
    public Mono<Order> deleteOrder(final String id) {
    
    
        return Mono.justOrEmpty(this.orders.remove(id));
    }
}

StubOrderService is used to perform basic CRUD operations on Order data. We use a ConcurrentHashMap object located in memory to store all Order object information, thus providing a stub code implementation.

The getOrdersByIds() method here is representative, and it receives the parameter ids of the Flux type. The parameters of the Flux type represent that there are multiple objects to be processed. Here, the flatMap operator introduced in "07 | Reactor Operator (Part 1): How to quickly convert the responsive flow?" is used to process each incoming id. This is also a very typical use of the flatMap operator.

In addition, the createOrUpdateOrder() method uses the Mono.doOnNext() method to convert the Mono object into an ordinary POJO object and save it. The doOnNext() method is equivalent to adding customized processing to the message every time the reactive flow sends an onNext notification.

With the stub service StubOrderService, we can create a StubOrderController to build a specific responsive RESTful service, which uses StubOrderService to complete specific endpoints.

The exposed endpoints of StubOrderController are very simple, just delegate specific functions to the corresponding method of StubOrderService:

@RestController
@RequestMapping("/orders")
public class StubOrderController {
    
    
 
    @Autowired
    private StubOrderService orderService;
 
    @GetMapping("")
    public Flux<Order> getOrders() {
    
    
        return this.orderService.getOrders();
    }
 
    @GetMapping("/{id}")
    public Mono<Order> getOrderById(@PathVariable("id") final String id) {
    
    
        return this.orderService.getOrderById(id);
    }
 
    @PostMapping("")
    public Mono<Void> createOrder(@RequestBody final Mono<Order> order) {
    
    
        return this.orderService.createOrUpdateOrder(order);
    }
 
    @DeleteMapping("/{id}")
    public Mono<Order> delete(@PathVariable("id") final String id) {
    
    
        return this.orderService.deleteOrder(id);
    }
}

WebFlux supports the same annotations as Spring MVC, the main difference is whether the underlying communication method is blocked:

  • Simple scenario, there is not much difference between the two
  • For complex applications, the advantages of responsive programming and back pressure will be reflected, which can improve the overall performance

4 Case Integration: Web Services in ReactiveSpringCSS

As a customer service system, the core business process is to generate customer service work orders, and the generation of work orders usually requires the use of user account information and associated order information.

The case contains three separate web services:

  • order-service for managing orders

  • account-service for managing user accounts

  • Core customer service customer-service

How services interact:

Interaction diagram of three services in ReactiveSpringCSS case system

Through this interactive diagram, the pseudocode of the core process of work order generation can be sorted out:

generateCustomerTicket {
    
    
 
  创建 CustomerTicket 对象
 
	从远程 account-service 中获取 Account 对象
 
	从远程 order-service 中获取 Order 对象
 
	设置 CustomerTicket 对象属性
 
	保存 CustomerTicket 对象并返回
}
  • [Get the Account object from the remote account-service]
  • [Get the Order object from the remote order-service]

Both involve access to remote Web services.

First, create corresponding HTTP endpoints in the account-service and order-service services respectively.

First, based on the annotation programming model, the implementation process of AccountController in account-service is given, the complete AccountController:

@RestController
@RequestMapping(value = "accounts")
public class AccountController {
    
    
 
    @Autowired
    private AccountService accountService;
 
    @GetMapping(value = "/{accountId}")
    public Mono<Account> getAccountById(@PathVariable("accountId") String accountId) {
    
    
 
        Mono<Account> account = accountService.getAccountById(accountId);
        return account;
    }
 
    @GetMapping(value = "accountname/{accountName}")
    public Mono<Account> getAccountByAccountName(@PathVariable("accountName") String accountName) {
    
    
 
        Mono<Account> account = accountService.getAccountByAccountName(accountName);
        return account;
    }
 
    @PostMapping(value = "/")
    public Mono<Void> addAccount(@RequestBody Mono<Account> account) {
    
    
        
        return accountService.addAccount(account);
    }
 
    @PutMapping(value = "/")
    public Mono<Void> updateAccount(@RequestBody Mono<Account> account) {
    
    
        
        return accountService.updateAccount(account);
    }
}

Several HTTP endpoints here are relatively simple, basically based on CRUD operations completed by AccountService. It should be noted that in the two methods of addAccount and updateAccount, the input parameter is a Mono object, not an Account object, which means that the AccountController will process the request from the client in a responsive flow.

Summarize

Starting today, we will introduce Spring WebFlux to build responsive RESTful web services. As a brand-new development framework, WebFlux has a wide range of application scenarios and supports two different development models. This lecture gives the development method of RESTful service for the annotation programming model.

FAQ

What are the connections and differences between using Spring WebFlux and Spring MVC to develop RESTful services?

Using Spring WebFlux and Spring MVC to develop RESTful services is based on the Spring framework, and they have the following connections and differences:

connect:

  1. Both can be used to develop RESTful services, and support HTTP protocol GET, POST, PUT, DELETE and other request methods.
  2. You can use annotations provided by Spring to simplify development, such as @RequestMapping, @GetMapping, @PostMapping, etc.
  3. You can use the interceptor provided by Spring to handle the logic before and after the request.

the difference:

  1. The programming model is different: Spring WebFlux is based on the reactive programming model and uses the Reactor library to handle asynchronous and non-blocking I/O operations, while Spring MVC is based on the traditional Servlet API and uses blocking I/O operations.
  2. The threading model is different: Spring WebFlux uses a small number of threads to handle a large number of concurrent requests, and implements non-blocking I/O operations through the event loop mechanism provided by the Reactor library. Spring MVC uses a thread pool to process requests, and each request will occupy a thread.
  3. Reactive support is different: Spring WebFlux supports reactive programming, and can use Mono and Flux types to handle asynchronous operations and streaming data. Spring MVC does not support reactive programming.
  4. Exception handling is different: The exception handling mechanism in Spring WebFlux is different from Spring MVC, which uses a functional programming model to handle exceptions. In WebFlux, an exception handler is a function that takes a ServerRequest object and a Throwable object, and returns a Mono object. In Spring MVC, the exception handler is a class that needs to implement the HandlerExceptionResolver interface.
  5. Different performance and concurrency: Since Spring WebFlux uses a small number of threads to handle a large number of concurrent requests, it can better protect the system from denial of service attacks. Spring MVC, on the other hand, needs to use a thread pool to process requests, which is vulnerable to denial of service attacks.

In short, choosing to use Spring WebFlux or Spring MVC depends on specific application scenarios and requirements. If you need to handle a large number of concurrent requests and want to use a responsive programming model to achieve high performance and high concurrency, you can choose Spring WebFlux; if the application scenario is relatively simple, you can choose Spring MVC.

The next article will continue to discuss the application of Spring WebFlux. We will analyze the programming components in the new functional programming model and complete the integration with ReactiveSpringCSS.

Guess you like

Origin blog.csdn.net/qq_33589510/article/details/131640216