spring 5に備えるリアクティブプログラミング入門
TRANSCRIPT
Copyright©2016 NTT corp. All Rights Reserved.
Spring 5 に備えるリアクティブプログラミング入門
2016 年 11 月 18 日
岩塚 卓弥,堅田 淳也NTT ソフトウェアイノベーションセンタ
2Copyright©2016 NTT corp. All Rights Reserved.
• 名前:岩塚 卓弥
• 所属: NTT ソフトウェアイノベーションセンタ• NTT の研究所のうちソフトウェアを専門に扱う• 自部署ではソフトウェア工学を研究• Spring ベースのグループ共通フレームワークの整備を担当
• Spring 関連:• Spring I/O, SpringOne それぞれ 2015 , 2016 に参加• 改訂新版 Spring 入門, Spring 徹底入門 レビュアー• JSUG 幹事
Introduction
3Copyright©2016 NTT corp. All Rights Reserved.
• 名前:堅田 淳也
• 所属: NTT ソフトウェアイノベーションセンタ• NTT の研究所のうちソフトウェアを専門に扱う• 自部署ではソフトウェア工学を研究• Spring ベースのグループ共通フレームワークの整備を担当
• Spring 経験:• 元 SIer で、プロジェクトへの Spring 適用支援などを担当• 使ったことのあるバージョン: Spring 1, 3, 4, (5)
Introduction
4Copyright©2016 NTT corp. All Rights Reserved.
Spring Framework 5.0
M1M2
M3 M4 GA2016/6
2016/92016/12
2017 1Q
• ベースラインのアップグレード• JDK 8+, Servlet 3.1+, JMS 2.0+, JPA 2.1+, JUnit 5
• コアコンテナのオーバーホール• 柔軟でプログラマティックな Bean 登録と解決
• 効率的なウェブアプリケーション• Reactive Streams ベースの web コントローラ• ラムダ式による HTTP ルーティングと処理
2016/11
5Copyright©2016 NTT corp. All Rights Reserved.
Spring Framework 5.0 / Reactor
spring-web-reactive
reactor-core
実装
依存
依存
For Reactive Programming
For Reactive Web
Spring Framework 5.0
Project Reactor
6Copyright©2016 NTT corp. All Rights Reserved.
What is reactive program
Transformational program始めに与えられた入力値を使って結果を出力
Interactive program必要なタイミングでユーザや他のプログラムとやりとり
Reactive program入力タイミングをコントロールできないソースに対応して動作
[G. Berry 1989] による分類
Program入力 出力
Program入力 A 次は入力 B をくれ
入力 B
Programソース Aソース B
ソースの変化に対応して変化
出力
出力
7Copyright©2016 NTT corp. All Rights Reserved.
Example (Reactive program)
22℃
℃ → °FProgram 71.6 °F
リアクティブシステム
与えられた物理環境に対して連続的にやりとりするシステム
8Copyright©2016 NTT corp. All Rights Reserved.
Example (Reactive program)
21.5℃
℃ → °FProgram 70.7 °F
リアクティブシステム
与えられた物理環境に対して連続的にやりとりするシステム
9Copyright©2016 NTT corp. All Rights Reserved.
Example (Reactive program)
21℃
℃ → °FProgram 69.8 °F
リアクティブシステム
与えられた物理環境に対して連続的にやりとりするシステム
10Copyright©2016 NTT corp. All Rights Reserved.
Invading namespace : Reactive System
The Reactive Manifesto* におけるリアクティブシステム
従来リアクティブシステムと呼ばれてきたものとは別物
名前の乗っ取りは良くない!
* http://www.reactivemanifesto.org/
11Copyright©2016 NTT corp. All Rights Reserved.
Classification of language forreactive programming
(Synchronous / Data flow) programming• ( 本来の意味での ) リアクティブシステムを記述する言語• 実時間の制約を持つ
• 入力の頻度や入出力のレスポンス時間に関する制約
Functional reactive programming(FRP)• 実時間の制約は無い• Behavior と Event , Switching combinator がプリミティ
ブとして提供される ( 後述 )
Cousins of reactive programming• 実時間の制約も Behavior も無い• 値の変更の伝播など,その他の RP の特徴のみを持つ• Reactive Streams はここに分類される[E. Bainomugisha et al. 2013] による分類
12Copyright©2016 NTT corp. All Rights Reserved.
• 「関数型言語の上で実装された RP=FRP 」ではない• Java で実装された FRP もある
• Frappé [A. Courtney 2001]• Event だけを扱う RP(≠FRP) が昨今の流行り
• イベントドリブンなアーキテクチャを実現するための RP
Behavior , Event , Switching combinator
Behavior
Event Switching combinator時間で連続変化する値
例:マウスポジション
Behavior mouseXBehavior mouseY
値変化の ( 無限 ) 列 データフローを動的に切り替えるための連結子
例:キー入力
a b c
Event keyStream
例:マウスクリックで描画色の切替え
right
leftBehavior color = switching( rc -> blue, lc -> red)
* 文法は適当
13Copyright©2016 NTT corp. All Rights Reserved.
Observer Pattern
Subject Observerobserve
notify
Q. イベントドリブンで値の変更の伝搬とか Observer パターンで実現できるのでは.
A. できます. 非常に雑に言うと Observer パターンの強いやつが 昨今の流行りです.
* http://reactivex.io
*
14Copyright©2016 NTT corp. All Rights Reserved.
Push based / Pull based reactive programming
Producer Consumer
Producer Consumer
Push ベースのRP
Pull ベースのRP
新しい値を片っ端から送信 頑張って処理
必要なときに欲しい分だけ要求
Consumer の要求に従って送信
✔ Producer で値が生成されてから Consumer が反応するまでのレイテンシが短い✘ Consumer の処理能力を超えて値が送信されてくる可能性がある
✔ 必要なとき (Consumer が処理可能な場合 ) にだけ新しい値を取得できる✘ Producer で値が生成されてから Consumer が反応するまで時間がかかる
15Copyright©2016 NTT corp. All Rights Reserved.
Reactive Streams
• 非同期ストリーム処理
• ノンブロッキングなバックプレッシャ• バックプレッシャ:受信側による送信制御を行う仕組み *
•標準を提供• 最低限のメソッドを備えたインタフェースと,仕様のみを提供
• Publisher, Subscriber, Subscription, Processor• 実用上必要なメソッドは各実装 (Reactor 他 ) が提供
* 元々はネットワーク用語.本来の用法から拡大されて使用されている. http://moccosblue.blogspot.jp/2015/05/translatebackpressure.html
ノンブロッキングなバックプレッシャを備えた非同期ストリーム処理の標準を提供するための提案 (直訳 ) c.f. パルスのファルシのルシがコクーンでパージ
Producer Consumer
この間のやりとりをノンブロッキングに
それぞれ非同期に処理できる
16Copyright©2016 NTT corp. All Rights Reserved.
Publisher, Subscriber, Subscription
Subscription Subscriber<T>request(long n)
Publisher<T> onSubscribe(Subscription s)
onNext(T t) × (0 〜 n)
onComplete() / onError(Throwable e)
…
subscribe(Subscriber<? super T> s)
• 各メソッドの戻り値の型はすべて void• Subscriber は送られてきたシグナルに対応して動作
• Push ベース RP / Pull ベース RP の両方を実現可能• request(Long.MAX_VALUE) とすると Push ベース RP になる
• Processor = Publisher ∧ Subscriber
17Copyright©2016 NTT corp. All Rights Reserved.
• Reactive Streams の各インタフェースを提供• JDK9 から利用可能• Reactive Streams とは別物扱いなので,相互変換が必要
java.util.concurrent.Flow
18Copyright©2016 NTT corp. All Rights Reserved.
Project Reactor
Reactor Core Reactor IPC Reactor Add-ons・ Reactor Core・ Reactor Core .Net・ Reactor Core JS
・ Reactor Netty・ Reactor Kafka・ Reactor Aeron
・ Reactor Adapter・ Reactor Test・ Reactor Logback
Reactive Streams の実装JDK9 の Flow との変換
エンコード / デコード通信 (UDP / TCP / HTTP)
他実装との変換その他便利系
Reactor Core を中心とした一連のプロジェクト群
19Copyright©2016 NTT corp. All Rights Reserved.
Mono, Flux
Publisher<T>
Mono<T> Flux<T>
0個または 1個の値を発行 0個以上の値を発行
• Reactor Core で提供される Publisher の実装• 発行され得る値の数によって使い分ける
• subscribe以外にストリーム処理用の様々なメソッドを提供
• filter, map, take, skip, …
20Copyright©2016 NTT corp. All Rights Reserved.
Flux.range(0, 10) .delayMillis(1000) .filter(n -> n%2 == 0) .map(n -> n*2);
Example (Flux)
9876543210
0123456789
86420
1612840
delayMillis(1000)
filter(n -> n%2 == 0)
filter(n -> n*2)
21Copyright©2016 NTT corp. All Rights Reserved.
Implementation detail
Flux のサブクラスのインスタンスが作成される.クラスはパッケージプライベートなので普通は意識しないで良い.
FluxRange
FluxFilter
FluxMap
source
source
Flux.range(..).filter(..).map(..);
22Copyright©2016 NTT corp. All Rights Reserved.
Implementation detail
FluxRange
FluxFilter
FluxMap
Flux.range(..).filter(..).map(..).subscribe(..);
source
source
Subscriber
subscribe
23Copyright©2016 NTT corp. All Rights Reserved.
Implementation detail
FluxRange
FluxFilter
FluxMap
Flux.range(..).filter(..).map(..).subscribe(..);
source
source
actual
actual
Subscriber
subscribe
new
FluxFilter.FilterSubscribe
r
FluxMap.MapSubscriber
24Copyright©2016 NTT corp. All Rights Reserved.
Implementation detail
FluxRange
FluxFilter
FluxMap
Flux.range(..).filter(..).map(..).subscribe(..);
source
source
actual
actual
Subscriber
subscribe
new
FluxFilter.FilterSubscribe
r
FluxMap.MapSubscriber
FluxRange.RangeSubscription
actual
actual
onSubscribes
s
s
25Copyright©2016 NTT corp. All Rights Reserved.
Implementation detail
FluxRange
FluxFilter
FluxMap
Flux.range(..).filter(..).map(..).subscribe(..);
source
source
actual
actual
Subscriber
subscribe
new
FluxFilter.FilterSubscribe
r
FluxMap.MapSubscriber
FluxRange.RangeSubscription
actual
actual
onSubscribes
s
s request
26Copyright©2016 NTT corp. All Rights Reserved.
Implementation detail
FluxRange
FluxFilter
FluxMap
Flux.range(..).filter(..).map(..).subscribe(..);
source
source
actual
actual
Subscriber
subscribe
new
FluxFilter.FilterSubscribe
r
FluxMap.MapSubscriber
FluxRange.RangeSubscription
actual
actual
onSubscribes
s
s request
onNext
27Copyright©2016 NTT corp. All Rights Reserved.
Don‘t return void
public void nonsense(Flux<Integer> s){s.map(n -> n*n);
}
public Flux<Integer> ok(Flux<Integer> s){ return s.map(n -> n*n);}
ダメな例
修正
「 subscribe されない = 何も起こらない」新しい Flux が生成されて捨てられるだけ
戻り値がどこかで subscribe されれば OK
28Copyright©2016 NTT corp. All Rights Reserved.
Subscriber for Reactive Web
@Controllerpublic Flux<Hoge> getHoges(){
…return resultFlux;
}誰が subscribe している?Servlet 3.1利用の場合
ServletHttpHandlerAdapter#service…
29Copyright©2016 NTT corp. All Rights Reserved.
Comparison from other typesCompletableFuture
Stream
Optional
✔ 非同期,ノンブロッキング ✔ 処理の合成
✘ Push にしか対応できない✘ ストリーム処理に対応できない
✔ ストリーム処理 ✔ 処理の合成
✘ 非同期処理のための API がない
✔ 0個または 1個の値を扱う (Reactor の Mono と同様 )
✔ 処理の合成✘ 非同期処理のための API がない
30Copyright©2016 NTT corp. All Rights Reserved.
• Reactor の祖先は Reactive Extensions(Rx)• Rx が最初に導入されたのは .NET• 当時 Microsoft に在籍していた Erik Meijer氏らが設計• Erik Meijer氏は関数型言語 (主に Haskell) の研究者としても知られている
• 関数型のプログラミングスタイルとは• 副作用を極力避けるプログラミングスタイル• そもそも実は「関数型言語」には明確な定義がない *
Functional programming style
Rx は関数型のプログラミングスタイルが活きるよう設計されている
* 東北大学 住井先生による Qiitaエントリ http://qiita.com/esumii/items/ec589d138e72e22ea97e
31Copyright©2016 NTT corp. All Rights Reserved.
•副作用を使用しない (=参照透明である )メリット• バグを作り込みにくくなる• 正しさを検証しやすくなる• 関数の独立性が高くなる
Referential Transparency
「状態」を持たないことによる複雑性と依存性の排除
いつどこで実行されるか分からない処理では参照透明性は特に重要
Reactive Streams においては…
・実際の計算は subscribe されるまで遅延される → どのタイミングで実行されるか分からない
・非同期的に実行される可能性がある → どのスレッドで実行されるか分からない
32Copyright©2016 NTT corp. All Rights Reserved.
• ラムダ式• 関数型インタフェースの実装を記述する構文 (from Java 8)• 名前をつけるまでもない小さな関数・述語等の記述に利用
•メソッド参照• 関数型インタフェースの実装として既存のメソッドを渡すため
の構文 (from Java 8)• 既存のメソッドを高階関数に渡す際に利用
•高階関数• 関数を引数や戻り値とする関数 (map, filter, …)• 関数を組み合わせて処理を記述するために利用
• タプル• 順序を持った値の組• 複数の値を返したい関数の定義等に利用
Tools for functional programming
Java
Reactor
33Copyright©2016 NTT corp. All Rights Reserved.
• 関数型言語で多く用いられている一種のデザインパターン• 関数をチェーンさせて,順番に値に適用させていくことができる
• Mono / Flux は Monad になっている• Stream や Optional, CompletableFuture も Monad
Monad
map(a.k.a fmap)
just(a.k.a unit, pure)
flatMap(a.k.a bind)
Monad が備える関数
Monad則, Functor , Applicative等との関係等,細かい諸々は省略
→ 全部同じような考え方で取り扱える
34Copyright©2016 NTT corp. All Rights Reserved.
Example
public Flux<Item> findItems(int idxA, int idxB){ return Mono.when(serviceA.findOne(idxA), serviceB.findOne(idxB)) .flatMap(p -> findItemsByXAndY( p.getT1.getX(), p.getT2.getY()));}
public Mono<long> getTotal(int idxA, int idxB){ return findItems(idxA, idxB) .map(Item::calculateScore) .reduce((a, b) -> a+b);}
35Copyright©2016 NTT corp. All Rights Reserved.
Example
public Flux<Item> findItems(int idxA, int idxB){ return Mono.when(serviceA.findOne(idxA), serviceB.findOne(idxB)) .flatMap(p -> findItemsByXAndY( p.getT1.getX(), p.getT2.getY()));}
public Mono<long> getTotal(int idxA, int idxB){ return findItems(idxA, idxB) .map(Item::calculateScore) .reduce(0, (a, b) -> a+b);}2つの Mono から, Mono のタプルを作る
36Copyright©2016 NTT corp. All Rights Reserved.
Example
public Flux<Item> findItems(int idxA, int idxB){ return Mono.when(serviceA.findOne(idxA), serviceB.findOne(idxB)) .flatMap(p -> findItemsByXAndY( p.getT1.getX(), p.getT2.getY()));}
public Mono<long> getTotal(int idxA, int idxB){ return findItems(idxA, idxB) .map(Item::calculateScore) .reduce((a, b) -> a+b);}
ラムダ式
タプルからの値の取り出し
高階関数
37Copyright©2016 NTT corp. All Rights Reserved.
Example
public Flux<Item> findItems(int idxA, int idxB){ return Mono.when(serviceA.findOne(idxA), serviceB.findOne(idxB)) .flatMap(p -> findItemsByXAndY( p.getT1.getX(), p.getT2.getY()));}
public Mono<long> getTotal(int idxA, int idxB){ return findItems(idxA, idxB) .map(Item::calculateScore) .reduce((a, b) -> a+b);} 高階関数 メソッド参照
38Copyright©2016 NTT corp. All Rights Reserved.
Example
public Flux<Item> findItems(int idxA, int idxB){ return Mono.when(serviceA.findOne(idxA), serviceB.findOne(idxB)) .flatMap(p -> findItemsByXAndY( p.getT1.getX(), p.getT2.getY()));}
public Mono<long> getTotal(int idxA, int idxB){ return findItems(idxA, idxB) .map(Item::calculateScore) .reduce((a, b) -> a+b);}
ラムダ式
そこまでの関数の適用結果と次の要素を繰り返し関数に適用していく高階関数 (畳み込み )
39Copyright©2016 NTT corp. All Rights Reserved.
• Reactor では,特に指定しない限り subscribe を呼びだしたスレッドでシグナルを処理する
• 多くの場合それが一番パフォーマンスが良い• 必要ならば明示的にバックグラウンド実行させる
• Reactor Core に含まれる Scheduler を利用する
Parallel processing
Flux.just(“red”, “blue”, “green”) .map(String::toUpperCase) .subscribeOn(Schedullers.parallel()).subscribe();
内部的に保持している複数のスレッドから一つを選んでタスクを実行
40Copyright©2016 NTT corp. All Rights Reserved.
• すべての要素を並列実行させるには,すべての要素を別々の Publisher に分配させる
Parallel processing
Flux.range(0, 100) .flatMap(n -> Mono.just(n*2) .subscribeOn(Scheduler.parallel()) ).subscribe();
別々の Publisher に分配
別のスレッドで実行
[2, 10, 18, 26, 34, 42, 50, …][0, 8, 6, 14, 22, 30, 38, …]flatMap :
concatMap :
• 実行順序は非決定的になる• flatMap の代わりに concatMap を使えば順序が担保される
[0, 2, 4, 6, 8, 10, 12, …]毎回結果が異なる
41Copyright©2016 NTT corp. All Rights Reserved.
• 非同期プログラミングはそもそも非常に複雑で難しい• Rx は上手くデザインされているが,非同期プログラミングの複雑さがすべて解消するわけではない
• 非決定性に起因する再現性の低いバグ• デバッグが難しい• テストが難しい• 設計が難しい• etc…
Complexity gain
→ 不必要に RP を導入することはデメリットの方が大きい
42Copyright©2016 NTT corp. All Rights Reserved.
Glitch
値の変更の伝搬の過程で一時的に不整合な状態になることがあるFlux<Integer> a = Flux.range(0,10);Flux<Integer> b = a.map(n -> n*2);Flux<Integer> c = Flux.combineLatest( b, a, (n, m) -> n+m);
a 0 1 2 3 4 5 6 7 8 9b 0 2 4 6 8 10 12 14 16 18c 0 3 6 9 12 15 18 21 24 27
理想
現実 a 0 1 1 2 2 3 3 4 4 …b 0 2 2 4 4 6 6 8 8 …c 0 2 3 5 6 8 9 11 12 …
b の変更が a の変更より先に c に伝搬される
43Copyright©2016 NTT corp. All Rights Reserved.
• ICSE’16 で RP のデバッグに関する研究が発表されている
• 対象は Scala• Eclipse プラグインとして RP 用デバッガを実装
• この領域はまだ他にも解決すべき課題は色々ありそう
Research effort
依存関係を可視化
ブレークポイントの設定
[G. Salvaneschi, M. Mezini 2016]
44Copyright©2016 NTT corp. All Rights Reserved.
Spring Web ReactiveSpring Framework 5.0
45Copyright©2016 NTT corp. All Rights Reserved.
Spring Framework 5.0Web MVC vs Web Reactive
@Controller, @RequestMapping
Spring MVC
Servlet API
Servlet Container
Spring Web Reactive
Reactive HTTP
Servlet 3.1, Netty, Undertow
46Copyright©2016 NTT corp. All Rights Reserved.
Spring Framework 5.0Web MVC vs Web Reactive
@Controller, @RequestMapping
Spring MVC
Servlet API
Servlet Container
Spring Web Reactive
Reactive HTTP
Servlet 3.1, Netty, Undertow
spring-web-reactive.jar が新規追加
New!
Servlet 3.1 で追加された Non-blocking I/O を利用Servlet以外の環境にも対応
従来の MVC と同様のアノテーションが使える
47Copyright©2016 NTT corp. All Rights Reserved.
• Spring Web MVC とは独立した存在• しかし、多くのアルゴリズムは MVC と共通• プログラミングモデルも MVC と変わらない
• @Controller, @RequestMapping, @RequestBody, etc…
• Reactive HTTP request/response を処理• リクエスト / レスポンスの読み書きを Non-Blocking で処理
• 少ないスレッド数でスケール
• 現在の実装状況など• Spring Framework 5.0 M3 が 2016/11/8 にリリース• まずは REST にフォーカスして実装してきた• HTML レンダリング系も徐々に実装されてきている
Spring Web Reactive Framework
48Copyright©2016 NTT corp. All Rights Reserved.
• Spring Initializr• http://start.spring.io
Getting start with Spring Boot
2.0.0(SNAPSHOT) を選択すると、Reactive Web が追加可能になる
49Copyright©2016 NTT corp. All Rights Reserved.
pom.xml
<dependencies> <dependency> <groupId>org.springframework.boot.experimental</groupId> <artifactId>spring-boot-starter-web-reactive</artifactId> </dependency> <!-- omit --></dependencies>
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot.experimental</groupId> <artifactId>spring-boot-dependencies-web-reactive</artifactId> <version>0.1.0.BUILD-SNAPSHOT</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies></dependencyManagement>
50Copyright©2016 NTT corp. All Rights Reserved.
• Controller から Flux や Mono が返せる• Flux/Mono の subscribe は呼ばない• subscribe するのは Spring
Reactive Web Controller
@RestControllerpublic class UserController {
@Autowired UserRepository userRepository;
@GetMapping("/find") public Mono<User> find(@RequestParam("id") long id) { return userRepository.findById(id); }
@GetMapping("/listAdult") public Flux<User> listAdult() { // 20歳以上のユーザを返す return userRepository.findAll() .filter(u -> u.getAge() >= 20); }}
public interface UserRepository { Mono<User> findById(long id); Flux<User> findAll(); Mono<Void> save(User user);}
/listAdult の結果
51Copyright©2016 NTT corp. All Rights Reserved.
• @ResponseBody(@RestController)メソッドの戻り値として返せる型
Reactive Web Controller
※ “User” は任意の JavaBean を表す
• Reactor• Mono<User>• Mono<Void>• Flux<User>• Flux<ServerSentEvent
>
• RxJava• Single<User>• Observable<User>• Flowable<User>
• Not reactive types• User• void
52Copyright©2016 NTT corp. All Rights Reserved.
• ダメな例• スレッドをブロックするメソッドを使って Flux/Mono から値
を取り出す
Reactive Web Controller
@GetMapping("/findBlocking")public User findBlocking(@RequestParam("id") long id) { return userRepository.findById(id).block();}
@GetMapping("/listAdultBlocking")public List<User> listAdult() { List<User> list = new ArrayList<>(); // 20歳以上のユーザを返す userRepository.findAll() .filter(u -> u.getAge() >= 20) .toIterable() .forEach(list::add); return list;}
User が返ってくるまでブロック !
Flux を Iterable に変換Iterable から User を取得するときにブロック !
53Copyright©2016 NTT corp. All Rights Reserved.
• Controller の引数• @RequestBody を付与することで、 JSON(Jackson) や
XML(JAXB) を受け取れる• 引数の型には Flux/Mono が指定可能
• Mono を使わずに単体の Bean で受けることもできる
• @PathVariable や @RequestParam も使える
Reactive Web Controller
@RequestMapping("/helloMono")public Mono<String> hello(@RequestBody Mono<User> user) { return user.map(u -> "Hello " + u.getName() + "!!");}
@RequestMapping("/hello")public Mono<String> hello(@RequestBody User user) { return Mono.just("Hello " + user.getName() + "!!");}
Mono<User>じゃなくてもブロッキングにはならない
54Copyright©2016 NTT corp. All Rights Reserved.
• @RequestBody が付与された引数に指定可能な型
Reactive Web Controller
※ “User” は任意の JavaBean を表す
• Reactor• Mono<User>• Flux<User>
• RxJava• Single<User>• Observable<User>
• Not reactive type• User
55Copyright©2016 NTT corp. All Rights Reserved.
• @ModelAttribute 引数への対応• 従来通り @ModelAttribute の省略も可能
Reactive Web Controller
@RequestMapping("/helloAnnotation")public Mono<String> helloAnnotation(@ModelAttribute("user") Mono<User> user) { return user.map(u -> "Hello " + u.getName());}
@RequestMapping("/hello")public Mono<String> hello(Mono<User> user) { return user.map(u -> "Hello " + u.getName());}
Spring 5.0 M3 から
POST /form/hello HTTP/1.1Host: localhost:8080Content-Type: application/x-www-form-urlencoded
id=10&name=taro&age=25
HTTP リクエスト
id= 10name= “taro”age= 25
:User
56Copyright©2016 NTT corp. All Rights Reserved.
• Bean Validation も使える• 従来通り @Validated による検証が可能• pom.xml に starter の追加が必要
Reactive Web ControllerBean Validation
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId></dependency>
57Copyright©2016 NTT corp. All Rights Reserved.
• Mono に対して @Validated を付与した場合は、検証失敗時に例外が流れてくる
Reactive Web ControllerBean Validation
@RequestMapping("/validate")public Mono<String> formValidate(@Validated User user, BindingResult bindingResult) { if (bindingResult.hasErrors()) { return Mono.just("Error!"); }}
@RequestMapping("/validateMono")public Mono<String> formValidate(@Validated Mono<User> user) { return user .map(u -> "Hello " + u.getName()) .otherwise(WebExchangeBindException.class, e -> Mono.just("Error!"));}
検証失敗時は WebExchangeBindException が発生
58Copyright©2016 NTT corp. All Rights Reserved.
• ちなみに・・・• Mono の引数に対して BindingResult の引数を追加すると
IllegalArgumentException 発生
Reactive Web ControllerBean Validation
// Not work!@RequestMapping("/validateMonoWithBindingResult")public Mono<String> form(@Validated Mono<User> user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) { ・・・
Caused by: java.lang.IllegalArgumentException: Errors/BindingResult cannot be used with an async model attribute. Either declare the model attribute without the async wrapper type or handle WebExchangeBindException through the async type.at org.springframework.util.Assert.isNull(Assert.java:126) ~[spring-core-5.0.0.BUILD-SNAPSHOT.jar:5.0.0.BUILD-SNAPSHOT]…
例外メッセージ
59Copyright©2016 NTT corp. All Rights Reserved.
• HTML5 のサーバプッシュ技術• Spring では 4.2 から対応している
Reactive Web Controller Server-Sent Events
@GetMapping("/connect")public SseEmitter connect() { SseEmitter sseEmitter = new SseEmitter(); sseEmitters.add(sseEmitter); sseEmitter.onCompletion(() -> sseEmitters.remove(sseEmitter)); return sseEmitter;}
@PostMapping("/send")public void send(@RequestBody Message message) { for (SseEmitter sseEmitter : this.sseEmitters) { try { sseEmitter.send(message, MediaType.APPLICATION_JSON); } catch (Exception e) { e.printStackTrace(); } }}
ControllerメソッドからSseEmitter を返す
Spring 4.3
60Copyright©2016 NTT corp. All Rights Reserved.
• Spring Web Reactive では• Controller から Flux を返すだけで Server-Sent Events に
対応• リトライ間隔などを細かく制御する場合
は、 Flux<ServerSentEvent> を返す
Reactive Web Controller Server-Sent Events
private FluxProcessor<Message, Message> processor = ReplayProcessor.<Message> create().serialize();
@GetMapping("/connect")public Flux<String> connect() { return processor.connect().map(m -> formatMessage(m));}
@PostMapping("/send")public Mono<Void> send(@RequestBody Mono<Message> message) { return message.doOnNext(m -> processor.onNext(m)).then();}
Flux を返すだけで OK
Spring 5Web Reactive
61Copyright©2016 NTT corp. All Rights Reserved.
• Servlet ベースでの起動の場合は・・・• Servlet3.1 から入った Non-blocking I/O を使ってノンブ
ロッキングを実現している
How to realize non-blocking
Controller から Flux や Mono を返せばいいのは分かったが、中はどうなっているのか?
62Copyright©2016 NTT corp. All Rights Reserved.
• Servlet 3.0 から入った非同期処理サポートと組み合わせて使う
•ネットワークの待ち時間からスレッドを解放• リクエストの受信待ち• レスポンスの送信待ち
•主要なインターフェース• AsyncContext• ReadListener• WriteListener
Servlet 3.1 – Non-blocking I/O
63Copyright©2016 NTT corp. All Rights Reserved.
Servlet 3.1 – Non-blocking I/O
Servlet
ReadListener
WriteListener
レスポンスが書き込めるようになったので、書き込み処理開始
リクエストが読み込めるようになったので処理開始
ReadListener, WriteListener を登録したら、 Servlet の処理は終了
64Copyright©2016 NTT corp. All Rights Reserved.
• Servlet の実装例
Servlet 3.1 – Non-blocking I/O
@WebServlet(urlPatterns = "/nonblocking", asyncSupported = true)public class MyNonBlockingServlet extends HttpServlet {
@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
AsyncContext asyncContext = req.startAsync(req, resp); ServletInputStream input = req.getInputStream(); ReadListener readListener = new ReadListenerImpl(input, resp, asyncContext); input.setReadListener(readListener); } //omit}
Servlet の非同期サポートを有効化
AsyncContext の取得
ReadListener インターフェースを実装したクラスを作成し、リスナとして登録
65Copyright©2016 NTT corp. All Rights Reserved.
• ReadListener の実装例
Servlet 3.1 – Non-blocking I/O
public class ReadListenerImpl implements ReadListener {
@Override // データ読み込み可能になるとコールバックされる public void onDataAvailable() throws IOException { int len; byte[] b = new byte[1024]; while (input.isReady() && !input.isFinished() && (len = input.read(b)) != -1) { sb.append(new String(b, 0, len)); } }
@Override // 全データを読み終わるとコールバックされる public void onAllDataRead() throws IOException { ServletOutputStream output = resp.getOutputStream(); WriteListener writeListener = new WriteListenerImpl(output, asyncContext, sb.toString()); output.setWriteListener(writeListener); }
@Override // エラー時にコールバックされる public void onError(Throwable throwable) {・・・ }
同様に WriteListener インターフェースを実装したクラスを作成し、リス
ナとして登録
66Copyright©2016 NTT corp. All Rights Reserved.
• WriteListener の実装例
Servlet 3.1 – Non-blocking I/O
public class WriteListenerImpl implements WriteListener {
@Override // データ書き込み可能になるとコールバックされる public void onWritePossible() throws IOException { output.print("<body>" + result + "</body>"); output.flush(); asyncContext.complete(); }
@Override // エラー時にコールバックされる public void onError(Throwable throwable) {・・・ }}
非同期処理の完了
67Copyright©2016 NTT corp. All Rights Reserved.
• ServletHttpHandlerAdapter• Servlet 3.1 の Non-blocking I/O を利用する Servlet の
実装クラス• Spring MVC の DispatcherServlet で行っていたような処
理は、この Servlet ではなく DispatcherHandler に委譲
Spring Web Reactive + Servlet 3.1
Servlet API
<<Servlet>>Dispatcher
Servlet
Servlet API
<<Servlet>> ServletHttp
HandlerAdapterDispatcher
Handler
Spring MVC Spring Web Reactive
68Copyright©2016 NTT corp. All Rights Reserved.
• ServletHttpHandlerAdapter
Spring Web Reactive + Servlet 3.1
@WebServlet(asyncSupported = true)@SuppressWarnings("serial")public class ServletHttpHandlerAdapter extends HttpHandlerAdapterSupport implements Servlet { // omit @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException {
// Start async before Read/WriteListener registration AsyncContext asyncContext = servletRequest.startAsync();
// omit
HandlerResultSubscriber resultSubscriber = new HandlerResultSubscriber(asyncContext); getHttpHandler().handle(request, response) .subscribe(resultSubscriber);}
ここで subscribeメソッドが呼ばれている
Servlet の非同期サポートを有効化
AsyncContext の取得
69Copyright©2016 NTT corp. All Rights Reserved.
Spring Web Reactive + Servlet 3.1ReadListener
private class RequestBodyReadListener implements ReadListener {
@Override public void onDataAvailable() throws IOException { RequestBodyPublisher.this.onDataAvailable(); }
@Override public void onAllDataRead() throws IOException { RequestBodyPublisher.this.onAllDataRead(); }
@Override public void onError(Throwable throwable) { RequestBodyPublisher.this.onError(throwable);
}}
ServletServerHttpRequest$RequestBodyPublisher$RequestBodyReadListener
70Copyright©2016 NTT corp. All Rights Reserved.
Spring Web Reactive + Servlet 3.1ReadListener
RequestBodyReadListener
RequestBodyPublisher
<<State>>DEMAND
リクエストの InputStream からデータを読み込む
SubscriberonDataAvailable()
onDataAvailable()
onDataAvailable()
readAndPublish()
onNext()
読み込んだデータを Subscriber へ
リクエストデータの読み込みを要求している状態を表すオブジェクト
71Copyright©2016 NTT corp. All Rights Reserved.
Spring Web Reactive + Servlet 3.1WriteListener
private class ResponseBodyWriteListener implements WriteListener { @Override public void onWritePossible() throws IOException { if (bodyProcessor != null) { bodyProcessor.onWritePossible(); }}
@Override public void onError(Throwable ex) { if (bodyProcessor != null) { bodyProcessor.cancel(); bodyProcessor.onError(ex); } }}
ServletServerHttpResponse$ResponseBodyWriteListener
72Copyright©2016 NTT corp. All Rights Reserved.
Spring Web Reactive + Servlet 3.1WriteListener
ResponseBodyWriteListener
ResponseBodyProcessor
<<State>>RECEIVED
レスポンスのOutputStream へ出力
SubscriptiononWritePossible()
onWritePossible()
onWritePossible()
write()
request(1)
( 出力が完了していなければ ) 次のデータを 1 つ要求
出力可能なデータを受け取っている状態を表すオブジェクト
73Copyright©2016 NTT corp. All Rights Reserved.
• Controllerメソッドの起動タイミング• 引数が Flux/Mono かどうかでタイミングが違う
Spring Web Reactive + Servlet 3.1
@RequestMapping("/helloMono")public Mono<String> hello(@RequestBody Mono<User> user) { return user.map(u -> "Hello " + u.getName() + "!!");}
@RequestMapping("/hello")public Mono<String> hello(@RequestBody User user) { return Mono.just("Hello " + user.getName() + "!!");}
メソッド起動の前には User(= リクエストデータ ) が必要 → ReadListener の onAllDataRead() 後に起動される
Mono<User> なので、 User( リクエストデータ ) はまだ不要→ ReadListener を待たずに、 Servlet の実行スレッドから起動される
74Copyright©2016 NTT corp. All Rights Reserved.
HTML rendering ?
75Copyright©2016 NTT corp. All Rights Reserved.
•状況• REST 対応にフォーカスして開発が進められてきたた
め、 HTML レンダリングなどの画面遷移系の対応は少し遅れぎみ
• テンプレートエンジン• Freemarker
• Spring との統合モジュールは Spring 側が提供• spring-web-reactive にサポートクラスがすでに存在する
• 基本的な HTML レンダリングは可能• 一応動きました
• Thymeleaf• Thymeleaf 自体に Reactive を意識した改善がすでに入っている• Spring との統合モジュールは Thymelaf 側が提供• 統合モジュールはまだ実験段階
• 一応動いてました
HTML rendering
76Copyright©2016 NTT corp. All Rights Reserved.
• 2016 年 5 月にリリース済み• 2016/9/28 に 3.0.2 をリリース
• Reactive フレームワークを意識した改善• Servlet API から独立• Engine throttling の導入
• Engine throttling• 出力チャネルからのバックプレッシャー要求に応じて、テンプ
レート処理結果を小分けで出力• 上記の出力処理を単一スレッドで実行• data-driven モードで動作させれば、 Publisher から流れて
くるデータに応じて、少しずつ処理結果を出力
Thymeleaf 3.0
77Copyright©2016 NTT corp. All Rights Reserved.
• Thymeleaf と Spring を統合するためのモジュール• メインリポジトリ上での Spring 5 対応はまだ
• Thymeleaf Sandbox: Spring + Spring Reactive
• Spring Web Reactive 対応の実験用リポジトリ• https://
github.com/thymeleaf/thymeleafsandbox-springreactive• ThymeleafView や ThymeleafViewResolver の
Reactive 対応版の実装が含まれている• 3 つのテンプレート処理モード
1. NORMAL2. BUFFERED3. DATA-DRIVEN
thymeleaf-spring
※ テンプレート処理モードについては ThymeleafView#renderFragmentInternal メソッド内のコメントが参考になる
78Copyright©2016 NTT corp. All Rights Reserved.
• Mode: DATA-DRIVEN• Model に格納された Publisher (Flux/Mono) の
onNext(X) に反応してレンダリングを実行していく• Non-blocking !!
Thymeleaf SandboxTemplate processing mode: DATA-DRIVEN
@RequestMapping("/biglist-buffered.thymeleaf") public String bigListBufferedThymeleaf(final Model model) {
final Publisher<PlaylistEntry> playlistFlow = this.playlistEntryRepository.findLargeCollectionPlaylistEntries(); // No need to fully resolve the Publisher! We will just let it drive model.addAttribute("dataSource", playlistFlow);
return "thymeleaf/biglist-buffered";}
Controller の実装例
Publisher を Model に格納できる !Flux/Mono でも OK(data-driven で処理するデータの Model Attribute 名を別途設定しておく必要あり )
79Copyright©2016 NTT corp. All Rights Reserved.
• Mode: DATA-DRIVEN• Model に格納された Publisher (Flux/Mono) の
onNext(X) に反応してレンダリングを実行していく• Non-blocking !!
Thymeleaf SandboxTemplate processing mode: DATA-DRIVEN
@RequestMapping("/biglist-buffered.thymeleaf") public String bigListBufferedThymeleaf(final Model model) {
final Publisher<PlaylistEntry> playlistFlow = this.playlistEntryRepository.findLargeCollectionPlaylistEntries(); // No need to fully resolve the Publisher! We will just let it drive model.addAttribute("dataSource", playlistFlow);
return "thymeleaf/biglist-buffered";}
Controller の実装例
Publisher を Model に格納できる !
Flux/Mono でも OK
Spring Framework 5.0 M3 で動かなくなりました
80Copyright©2016 NTT corp. All Rights Reserved.
• Spring 5.0 M3 では…• レンダリング前に Model内の Publisher(Flux/Mono) が解決
されるようになった• Thymeleaf が Model からオブジェクトを取り出すときには、
Publisher(Flux/Mono) ではなくなっている…
Thymeleaf SandboxTemplate processing mode: DATA-DRIVEN
@RequestMapping("/biglist-buffered.thymeleaf") public String bigListBufferedThymeleaf(final Model model) {
final Publisher<PlaylistEntry> playlistFlow = this.playlistEntryRepository.findLargeCollectionPlaylistEntries(); // No need to fully resolve the Publisher! We will just let it drive model.addAttribute("dataSource", playlistFlow);
return "thymeleaf/biglist-buffered";}
Controller の実装例
Publisher を入れてるが、 ThymeleafView が取り出すときには Publisher ではなくなっている
81Copyright©2016 NTT corp. All Rights Reserved.
Database ?
82Copyright©2016 NTT corp. All Rights Reserved.
• NoSQL• MongoDB
• Reactive Streams driver• Couchbase
• RxJava driver• Redis
• lettuce• RDB
• JDBC に Non-Blocking な仕組みがない• PostgreSQL と MySQL の async-driver というものが
GitHub 上に見つかるが・・・• PostgreSQL / MySQL
• https://github.com/mauricio/postgresql-async• PostgreSQL
• https://github.com/alaisi/postgres-async-driver
Database
Spring Data 2.0 で対応予定
83Copyright©2016 NTT corp. All Rights Reserved.
MongoDB - Reactive Streams driver
@Repositorypublic class MongoRepository {
private final ObjectMapper mapper; private final MongoCollection<Document> col;
@Autowired public MongoRepository(MongoDatabase db, ObjectMapper mapper) { this.mapper = mapper; this.col = db.getCollection("user"); }
public Mono<Void> insert(Mono<User> user) { return user.flatMap(u -> col.insertOne(Document.parse(toJson(u)))).then(); }
<dependency> <groupId>org.mongodb</groupId> <artifactId>mongodb-driver-reactivestreams</artifactId> <version>1.2.0</version></dependency>
pom.xml
Repository
Publisher を返してくる
@BeanMongoDatabase mongoDatabase() { return MongoClients.create().getDatabase("demo");}
MongoConfig
84Copyright©2016 NTT corp. All Rights Reserved.
• JavaOne 2016 (9/18-22) にて Non-blocking JDBC に関するセッションがあった
• セッション名 : • “JDBC Next – A new non-blocking API for connecting to a
database”• 発表者は Oracle の人で、 JDBC Expert Group メンバ
•内容• 現行 JDBC の拡張や置き換えではなく、現行 JDBC と選択する
ものになる ( らしい )• Oracle が Oracle DB のドライバとしてプロトタイプを実
装した ( らしい )• 今後は JDBC Expert Group に開発が引き継がれる ( らしい )• Java10? ( まだまだ未定っぽい )
Non-Blocking JDBC API ?
※ https://static.rainfocus.com/oracle/oow16/sess/1461693351182001EmRq/ppt/CONF1578%2020160916.pdf
85Copyright©2016 NTT corp. All Rights Reserved.
Future
86Copyright©2016 NTT corp. All Rights Reserved.
• M4 で対応 ? (予定は未定 )• [SPR-14527] Reactive WebSocket adapter layer
• WebSocketHandler のリアクティブ版
• [SPR-14546] Reactive multipart request support• Multipart リクエストへの対応
• [SPR-14534] Reactive HTTP response based RedirectView• “redirect:” プリフィクスによるリダイレクトに対応
• [SPR-14535] Reactive request and response in SpEL expression within @MVC annotations
• Controllerメソッド等に使用するアノテーションの SpEL内で、 request やresponse を参照できるようにする
• @PathVariable, @RequestParam, @RequestHeader, etc
• など…
Future
87Copyright©2016 NTT corp. All Rights Reserved.
Enjoy Reactive !!
88Copyright©2016 NTT corp. All Rights Reserved.
• Spring Framework Reference Documentation 5.0.0 M3 - 23. Web Reactive Framework• h t tp: / /docs .s p r ing . io / s p r ing - f r a mewo rk /do cs /5 .0 .0 .M3 / s p r ing - f ra m ewo r k - re fe rence / h tm l / web - rea c t i v e .h tml
• The Spr ing Blog• No tes on Reac t iv e Pro g r am m ing Pa r t I : T he Reac t iv e La ndsca pe
• h t tp s : / /s p r i n g . i o / b l o g / 2 0 1 6 / 0 6 / 0 7 / n o t e s - o n - re a c t i ve - p ro g r a m m i n g - p a r t - i - th e - re a c t i v e - l a n d s c ap e• No tes on Reac t iv e Pro g r am m ing Pa r t I I : Wr i t i ng So m e Co de
• h t tp s : / /s p r i n g . i o / b l o g / 2 0 1 6 / 0 6 / 1 3 / n o t e s - o n - re a c t i ve - p ro g r a m m i n g - p a r t - i i - w r i t i n g - s o m e- c o d e• No tes on Reac t iv e Pro g r am m ing Pa r t I I I : A S im p le HTT P Se rv er A pp l i ca t io n
• h t tp s : / /s p r i n g . i o / b l o g / 2 0 1 6 / 0 7 / 2 0 / n o t e s - o n - re a c t i ve - p ro g r a m m i n g - p a r t - i i i - a - s i m p l e - h t t p - s e r v e r- a p p l i c a t i o n• Under s ta nd ing Rea c t i v e t y pes
• h t tp s : / /s p r i n g . i o / b l o g / 2 0 1 6 / 0 4 / 1 9 / u n d e r s ta n d i n g - re a c t i v e - ty p e s• thymeleafsandbox-spr ingreactive
• h t tps: / /g i thub .co m/ thy m elea f / thy m elea fs a ndbox - s p r ing rea c t i v e• MongoDB React ive Streams Java Dr iver
• h t tps : / / m o ng odb .g i t hub . io / m ongo - ja va -d r i v e r- rea c t i ves t rea ms/• Database async dr iver
• Po s t g res -a s ync -d r i v er• h t tp s : / / g i t h u b . c o m / a l a i s i / p o s t g re s - a s y n c - d r i ve r
• Po s t g reSQL a nd MySQL a sy nc d r iv e r• h t tp s : / / g i t h u b . c o m / m a u r i c i o / p o s t g re s q l - a s y n c
• サンプルコード• Spr ing - reac t i ve -p lay g ro un d (Sp r ing Web Rea c t i v e と Mo ngo / Coucheba se / Pos t greSq l のサンプル )
• h t tp s : / / g i t h u b . c o m / s d e l eu z e / s p r i n g - re a c t i v e - p l a y g ro u n d• スライド
• Im per a t i ve t o Rea c t i v e Web A pp l i ca t io ns• h t tp : / / w w w. s l i d e s h a re . n e t / S p r i n g C e n t r a l / i m p e r a t i v e - t o - re a c t i v e - w e b - a p p l i c a t i o n s
• React ive Web アプリケー ション - そして Spr ing 5 へ• h t tp : / /w w w. s l i d e s h a re . n e t / m a k i n g x / re a c t i v e - w e b - s p r i n g - 5 - j j u g c c c - c c c e f3
• Ser v l e t 3 .1 A s ync I / O• h t tp : / /w w w. s l i d e s h a re . n e t / S i m o n e B o rd e t / s e r v l e t - 3 1 - a s y n c - i o
• JDBC N ex t – A new non -b lo ck ing A P I fo r co nnec t ing to a da ta ba s e• h t tp s : / /s t a t i c . r a i n fo c u s . c om / o r a c l e / o o w 1 6 / s e s s / 1 4 6 1 6 9 3 3 5 1 1 8 2 0 0 1 E m Rq / p p t / C O N F 1 5 7 8 % 2 0 2 0 1 6 0 9 1 6 . p d f
• 書籍• パー フェクト J a va EE
• h t tp: / /g i h y o. j p / b o o k / 2 0 1 6 / 9 7 8 - 4 - 7 7 4 1 - 8 3 1 6 - 9• Spr ing 徹 底 入 門 Spr ing Fra m ewo r k による J a va アプリケー ション開発
• h t tp: / /w w w. s h o e i s h a . c o . j p / b oo k / d e t a i l / 9 7 8 4 7 9 8 1 4 2 4 7 0• 論文
• Gu ido Sa lva nesch i , M i ra Mez in i . 2016 . Debugg ing rea ct i ve p ro g ra m ming w i t h rea ct i ve in s pect o r.• Eng inee r Ba ino mug is ha , A ndo n i Lo m bide Ca rre to n , To m Van Cu ts em, S t i j n Mos t inck x , Wo l fga ng De Meut er. 2 013 . A s ur vey o n reac t i ve
p ro gr a mm ing .• Gér ard Be rr y. 1989 . Rea l T ime Pro g ra m ming : Spec ia l Pu rpo s e o r G ener a l Pu rpo s e La ngua ges .• Ant ony Cou r tney. 2001 .Fra ppé : Func t io na l Rea c t i ve Prog ra mm ing in Ja v a .
参考文献