[Day 23] Reactive Programming - Spring WebFlux(Handler)

前言

经过上一个范例的练习,也大致上的知道相较於原本Spring MVC annotation-based,Spring WebFlux更倾向使用更Functional的Handler&Route,难道这样就够了吗?
https://ithelp.ithome.com.tw/upload/images/20211007/201414189Fb3EVWXI4.png

图片来源:网路

Handler

很显然这样的练习连小专案都不算,所以接下来介绍进阶一点点,开始有了CRUD,常常戏称CRUD工程师,就是要从CRUD学起。
首先下方有一个很基本的RestController,有GETPOST,有PathVariableRequestBody,最上方也有RequestMapping能够统一路径,这次就来把他改写为Router + Handler模式。

@RestController 
@RequestMapping("/mvc/greeting") 
@RequiredArgsConstructor 
public class GreetingController { 
	private final GreetingRepository repository; 
	@GetMapping 
	public Flux<Greeting> allPeople() { 
		return this.repository.allGreeting(); 
	} 
	@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) 
	public Mono<Void> saveGreeting(@RequestBody Mono<Greeting> greetingMono) { 
		return this.repository.saveGreeting(greetingMono); 
	} 
	@GetMapping("/{id}") 
	public Mono<Greeting> getGreeting(@PathVariable int id) { 
		return this.repository.getGreeting(id); 
	} 
}

第一步先将逻辑的部份抽到Handler中,因为不能确定何时才会将资料回传,所以回传ServerResponse都会加上Reactive的Mono or Flux,Body里面要提供type class是因为Flux的资料不是马上就会存在,而是随着时间传入,也就是第一时间是没办法知道里面的资料型态,所以我们要提前先指定好传入。

 public Mono<ServerResponse> allGreeting(ServerRequest request) { 
    Flux<Greeting> greetingFlux = this.repository.allGreeting(); 
    return ServerResponse.ok().contentType(APPLICATION_JSON).body(greetingFlux, Greeting.class); 
  }

这边的bodyToMono,就是之前spring mvc@RequestBody直接将Body转成物件,这边一样预设是透过jackson

  public Mono<ServerResponse> saveGreeting(ServerRequest request) { 
    Mono<Greeting> greetingMono = request.bodyToMono(Greeting.class); 
    return ServerResponse.ok().build(this.repository.saveGreeting(greetingMono)); 
  }

@PathVariable取代掉,参考上面的结果很直觉就可以写出下面的程序码,但是当传入一个不存在的ID,仍回传两百,如果想要回传404则须调整作法,也就是现在会有两种ServerResponse,一种正常回传两百,一种找不到回传404,根据传统的直觉你可就直接if else或是三元,但在Reactive的世界中,程序执行的当下很有可能是还没有资料进来的,也就是永远只回传404,如果你停下来等待结果,则又走回了blocking的老路。

public Mono<ServerResponse> getGreeting(ServerRequest request) { 
    int id = Integer.parseInt(request.pathVariable("id")); 
    Mono<Greeting> greetingMono = this.repository.getGreeting(id);
    //Mono<ServerResponse> build = ServerResponse.notFound().build();
    return ServerResponse.ok().contentType(APPLICATION_JSON).body(greetingMono, Greeting.class);
  }

这时候Reactor有提供switchIfEmpty让你可以很灵活很Functional的判断。

  public Mono<ServerResponse> getGreeting(ServerRequest request) { 
    int id = Integer.parseInt(request.pathVariable("id")); 
    Mono<Greeting> greetingMono = this.repository.getGreeting(id); 
    return greetingMono 
        .flatMap(greeting -> ServerResponse.ok().contentType(APPLICATION_JSON).body( 
            BodyInserters.fromValue(greeting))) 
        .switchIfEmpty(ServerResponse.notFound().build()); 
  }

最後成果如下,下一篇来介绍Router

@Component
@RequiredArgsConstructor
public class GreetingHandler {

  private final GreetingRepository repository;

  public Mono<ServerResponse> hello(ServerRequest request) {
    return ServerResponse.ok().contentType(APPLICATION_JSON)
        .body(BodyInserters.fromValue(new Greeting("Hello, Spring!")));
  }
  public Mono<ServerResponse> getGreeting(ServerRequest request) {
    int id = Integer.parseInt(request.pathVariable("id"));
    Mono<Greeting> greetingMono = this.repository.getGreeting(id);
    return greetingMono
        .flatMap(greeting -> ServerResponse.ok().contentType(APPLICATION_JSON).body(
            BodyInserters.fromValue(greeting)))
        .switchIfEmpty(ServerResponse.notFound().build());
  }

  public Mono<ServerResponse> saveGreeting(ServerRequest request) {
    Mono<Greeting> greetingMono = request.bodyToMono(Greeting.class);
    return ServerResponse.ok().build(this.repository.saveGreeting(greetingMono));
  }

  public Mono<ServerResponse> allGreeting(ServerRequest request) {
    Flux<Greeting> greetingFlux = this.repository.allGreeting();
    return ServerResponse.ok().contentType(APPLICATION_JSON).body(greetingFlux, Greeting.class);
  }
}

结语

感觉得出来Spring想尽办法降低原本使用Spring Mvc的开发者学习Spring WebFlux的门槛。

资料来源

<<:  [NestJS 带你飞!] DAY22 - MongoDB

>>:  [Day25]-开发GUI程序使用tkinter2

D-9. Rails API-Only 实作 && House Robber

API Application Programming Interface的缩写,主要在I,一个接口...

[Day19]-档案读取与写入

写入档案 写入空文件内 档案很大时分段写入 Shutil模组 档案或目录的复制、移动、更改和删除,...

如何在Windows 10中创建系统映像

系统映像是Windows作业系统平稳运行所需的磁碟机副本。如果您的PC的HDD或SSD出现故障,则可...

【Day22-图表】文不如表,表不如图——使用seaborn一行透过图表观察资料!

我们人类是视觉的动物,因此在面对非常大量的资料的时候有一个合适、快速、有效的视觉化方法绝对是非常有必...

#9 - Creating & Removing Directories

今天要学习的是如何新增和删除资料夹,一样是用昨天的 fs modules,不过在新增资料夹之前,先来...