Spring WebFluxを試す (主にRouter Functions)

# 概要
Java Day Tokyo 2017で[Spring Framework 5.0による Reactive Web Application](https://www.slideshare.net/makingx/spring-framework-50-reactive-web-application-javadaytokyo)を聞いて軽くテンション上がったのでSpring5から追加される[Spring WebFlux](http://docs.spring.io/spring/docs/5.0.0.RC1/spring-framework-reference/web.html#web-reactive)を試してみた。

参考資料はこちら。
[はじめてのSpring WebFlux (その1 - Spring WebFluxを試す)](https://blog.ik.am/entries/417)
著者は登壇者と同じToshiaki Makiさん。
(というかセッションでご本人が「ブログ書いてるんで、それ読めば試せます」的なことを仰っていた)

---

# とりあえずアプリを起動するまで
今回はSpring Bootの2.0.0 M1を使用。
(Spring Bootは2.0.0からSpring5に対応しているため)

Spring Initalizerでさっくり作成する。
Spring Bootのバージョンを2.0.0 M1にして、"Reactive Web"を追加。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/72808/3a27bf1a-d4a5-eca7-6f8d-43a6b5e91f04.png)
(ちなみに[M1時点ではactuatorは非対応](http://disq.us/p/1gienpj)。残念)

で、起動してNettyが立ち上がったのを確認したひとまずOK。
以下起動ログの抜粋。

```
2017-05-20 14:14:17.447  INFO 4880 --- [  restartedMain] o.s.w.r.r.m.a.ControllerMethodResolver   : Looking for @ControllerAdvice: org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext@30361c29: startup date [Sat May 20 14:14:16 JST 2017]; root of context hierarchy
2017-05-20 14:14:17.916  INFO 4880 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2017-05-20 14:14:17.944  INFO 4880 --- [  restartedMain] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-05-20 14:14:18.241  INFO 4880 --- [  restartedMain] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
2017-05-20 14:14:18.246  INFO 4880 --- [  restartedMain] itaka.demo.WebfluxSampleApplication      : Started WebfluxSampleApplication in 2.181 seconds (JVM running for 3.946)
```

---

# Router Functionsでルーティング定義
WebFluxでは`RouterFunction<ServerResponse>`な`@Bean`があればそれがルーティング定義として扱われる。
なので、Component Scan対象のクラスに書けばどこでも良さそう。

```java
// いくつかstatic importしているので、詳細は参考資料のほうを参照
@Bean
public RouterFunction<ServerResponse> routes() {
    return route(GET("/"), req -> ok().body(Flux.just("Hello", "WebFlux"), String.class));
}
```

で、これで起動して`http://localhost:8080/`にアクセスしたら"HelloWebFlux"が返ってくる。
以上。

## PathVariableどう扱うの?
1. RequestPredicate(`GET`とか`POST`とか)の引数に今までどおり"{var}"のように記述
1. ServerRequestの`.pathVariable()`で取得

の手順でいける。

```java
@Bean
public RouterFunction<ServerResponse> routes() {
    return RouterFunctions
            .route(GET("/hello/{name}"), req ->
                    ok().body(Flux.just("Hello", req.pathVariable("name")), String.class));
}
```

## Routingの優先順位
上記のPathVariableのroutingと"/hello/hoge"という固定パス定義によるroutingを両方定義した場合、何が起こるか。
結論から言うと、__先に書いたほうが優先される__。シンプル。

### PathVariableのroutingを先に書いた場合

```java
RouterFunctions
 .route(GET("/hello/{name}"), req -> ...)
 .AndRroute(GET("/hello/hoge"), req -> ...);
```
-> PathVariableが優先される。

### 固定パス定義のroutingを先に書いた場合

```java
RouterFunctions
 .route(GET("/hello/hoge"), req -> ...)
 .AndRroute(GET("/hello/{name}"), req -> ...);
```
-> 固定パス定義が優先される。

## Routing定義が増えてきたら
サンプル程度の規模であれば上述のようにダラダラroute定義をつなげていっても問題ないが、これは実際のアプリケーションで言えば__1つのControllerクラスに全Mappingを記述していることになる__ので、よろしくない。
そこで、以下のようにすることで多少すっきり書くことができる。

1. `RouterFunction<ServerResponse>`を返却するメソッドを作って別クラスに置く
2. 元の`@Bean`定義しているメソッド内で1のメソッドを呼び出すようにする


```java:HelloRouter.java
@Component
public class HelloRouter {

    private static final String PATH = "/hello";

    public RouterFunction<ServerResponse> route() {
        return RouterFunctions
                .route(GET(PATH), this::hello)
                .andRoute(GET(PATH + "/hoge"), this::helloHoge)
                .andRoute(GET(PATH + "/{name}"), this::helloName);
    }

    private Mono<ServerResponse> hello(ServerRequest req) {
        return ok().body(Flux.just("Hello", "WebFlux"), String.class);
    }

    private Mono<ServerResponse> helloName(ServerRequest req) {
        return ok().body(Flux.just("Hello", req.pathVariable("name")), String.class);
    }

    private Mono<ServerResponse> helloHoge(ServerRequest req) {
        return ok().body(Flux.just("Hoge", "Hoge"), String.class);
    }
}
```

```java:HelloRouterのroute()を呼び出す
@Bean
public RouterFunction<ServerResponse> routes(HelloRouter helloRouter) {
    return helloRouter.route();
}
```

更に、`.and`によるメソッドチェーンで複数の`RouterFunction<ServerResponse>`を繋げることができる。



```java:.and()を使って複数のRouterFunctionをがっちゃんこする
// StreamRouterでは"/strem"のroutingを定義してあるものとする
@Bean
public RouterFunction<ServerResponse> routes(HelloRouter helloRouter, StreamRouter streamRouter) {
    return helloRouter.route()
            .and(streamRouter.route());
}
```

これで以下のroutingがセットされる。

- /hello
- /hello/hoge
- /hello/{name}
- /stream 

### (追記) RouterFunctions.nest()による改善
[コメントをいただいた](http://qiita.com/IsaoTakahashi/items/9c1322c84ef89f3f562b#comment-907393fc85fab05fc37d)ので、`nest()`を使って上のコードを更に書き換えてみる。
(Makiさんありがとうございます！)

```java:RouterFunctions.nest()で階層状にrouting定義
public RouterFunction<ServerResponse> routes() {
    return nest(path("/hello"),
            route(GET("/"), this::hello)
                .andRoute(GET("/hoge"), this::helloHoge)
                .andRoute(GET("/{name}"), this::helloName));
}
```
ついでに`RouterFunctions.route()`もstatic importしたので、大分スッキリした！
RouterFunctionの部分だけ見てRouting内容を把握できるので、ここも`@Controller`での定義よりも良い所かな。

---

# 無限Streamで遊ぶ
ServerResponseの`.body()`でセットしている`Flux`というのはReactive StreamのPublisherを実装しているクラスで、イイ感じにReativeなStreamを実現できるそう。

具体例として「アクセスしたら延々と数字が返ってきて、その数字がカウントアップされていく」ものを実装してみる。
方法は以下の通り。

1. 無限Streamを作る
2. 1のStreamからFluxを作る
3. 2のFluxをbodyとしてセット


```java
@Bean
public RouterFunction<ServerResponse> routes() {
    Stream<Integer> stream = Stream.iterate(0, i -> i + 1);
    Flux<Integer> flux = Flux.fromStream(stream).delayElements(Duration.ofSeconds(1));

    return RouterFunctions
            .route(GET("/stream"), req ->
                    ok().contentType(MediaType.APPLICATION_STREAM_JSON).body(flux, Integer.class));
}
```

これで、`http://localhost:8080/stream`にアクセスすると延々と数字が流れてくる。
ただそのままだとものすごい勢いで流れてくるので、上記例では`.delayElements()`を使用してインターバルを持たせて流している。

---

# まとめ
「WebFluxを試す」と言っておきながら、半分以上Router Functionsの実験になってしまった。
「WebFluxすげー」って体感するためには、もうちょい複雑というか「Non Blockingだから超速い」みたいなことが分かるようなアプリケーションを作ったほうが良さそう。

Router FunctiuonsはJava Day Tokyoのセッションを聞いていたときよりもテンション高く遊べた。
今回は`GET`しか使わなかったが、もちろん他のHttpMethodも使えるし、`.and()`や`.or()`、`.negate()`などを使って複雑なrouting条件を作ることもできるので楽しそう。
ただ、現時点(Spring Boot 2.0.0)では`@Controller`との共存ができないらしいので既存アプリケーションに適用するのはちょっとツライ(全部書き換えなくちゃいけない)･･･。
やるなら新規アプリケーションでやったほうが良いかな。

今回試したもののコードは以下の通り。
(書きながらリファクタリングしていったので、この記事のコードとは若干ズレがある)
[IsaoTakahashi/webflux-sample - GitHub](https://github.com/IsaoTakahashi/webflux-sample)