ORACLE.COM/JAVAMAGAZINE ////////////////////// JANUARY/FEBRUARY 2018
50
//reactive programming /
リアクティブ・プログラミングは、非同期I/Oを利用するソフトウェアを書くためのアプローチです。非同期I/O
は、ソフトウェアにとっての大きな変化の前触れとなる小さなアイデアです。このアイデア自体はシンプル
で、I/Oアクティビティを待つ間にアイドル状態となってしまうリソースを活用することで、リソースの非効率な使い方
を改善しようというものです。非同期I/Oは、I/O処理の一般的な設計を逆転させるものです。クライアントは、新し
いデータを要求するのではなく、新しいデータについての通知を受けます。このアプローチを使うと、クライアント
はI/O待ちから解放されて、新しい通知を待つ間に別のことを行えるようになります。
もちろん、大量の通知が殺到してクライアントの処理が追いつかなくなるというリスクは常に存在します。そ
のため、クライアントでは、処理できない作業を拒否できるようにする必要があります。これは、分散システムにお
けるフロー制御の基本的な側面です。リアクティブ・プログラミングでは、クライアントが扱うことができる作業の
量を知らせる機能をバックプレッシャーと呼んでいます。
リアクティブ・プログラミングは、Akka Streams、Vert.x、RxJavaなど、多くのプロジェクトでサポートされていま
す。[編集注:Vert.xとRxJavaは、本号21ページの記事「Eclipse Vert.xとRxJavaでリアクティブ・プログラミング」で特
集しています。] Springチームには、Spring Frameworkにリアクティブ機能を追加するReactorというプロジェクトが
あります。これらの各種アプローチに共通する土台が、Reactive Streamsイニシアチブにまとめられています。この
イニシアチブが、いわば非公式な標準になっています。
基礎となるデータ型Reactive Streamsイニシアチブでは、4つのデータ型が定義されています。Publisherは、やがて到着する可能性が
ある値を生成するプロデューサで、T型の値を生成します。リスト1をご覧ください。
リスト1:Reactive StreamsのPublisher<T>
リアクティブSpringSpring Frameworkの基礎から、リアクティブなアプリケーションの迅速な構築へ
JOSH LONG
ORACLE.COM/JAVAMAGAZINE ////////////////////// JANUARY/FEBRUARY 2018
51
//reactive programming /
package org.reactivestreams;
public interface Publisher<T> { void subscribe(Subscriber<?Super T> s); }
Subscriberは、Publisherをサブスクライブし、T型の新しい値についての通知を受け取ります。リスト2をご覧くだ
さい。
リスト2:Reactive StreamsのSubscriberpackage org.reactivestreams; public interface Subscriber<T> { public void onSubscribe(Subscription s); public void onNext(T t); public void onError(Throwable t); public void onComplete(); }
SubscriberがPublisherをサブスクライブすると、Subscriptionを得ます。リスト3をご覧ください。
リスト3:Reactive StreamsのSubscription package org.reactivestreams; public interface Subscription { public void request(long n); public void cancel(); }
SubscriberでもあるPublisherは、Processorと呼ばれます。その例をリスト4に示します。
ORACLE.COM/JAVAMAGAZINE ////////////////////// JANUARY/FEBRUARY 2018
52
//reactive programming /
リスト4:Reactive StreamsのProcessor package org.reactivestreams;
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> { }
この仕様は、実装のための決まりごとではなく、相互運用性のある型を定義することを意図したもので
す。Reactive Streamsの型は、ついにJava 9に取り入れられました。Java 9のjava.util.concurrent.Flowクラスに
は、これらの型と意味的に1対1の関係になっている等価なインタフェースが含まれています。
Reactor Reactive Streamsの型だけでは十分とは言えません。フィルタリングや変換などの操作に対応するためには、さら
に高次の実装が必要になります。そのためのよい選択肢となるのが、PivotalのReactorプロジェクトです。Reactor
は、Reactive Streams仕様をベースに構築されており、2種類の専用のPublisher<T>を提供しています。1つ目の
Fluxは、0個以上の値を生成するPublisherです。値は無限に生成できます。2つ目のMono<T>は、1個または0個
の値を生成するPublisherです。この2つはいずれもパブリッシャであり、パブリッシャとして扱うことができます
が、Reactive Streams仕様で定められている内容よりもはるかに多くのことを実現できます。この2つは、いずれも
値のストリームを処理する方法を提供します。Reactorの型は、見事に構成されており、あるものからの出力を別の
ものの入力として使うことができるようになっています。
リアクティブSpring Reactorプロジェクトは便利ですが、土台にすぎません。アプリケーションは、データソースと通信する必要があ
り、HTTP、Server-Sent Events(SSE)、またはWebSocketエンドポイントを生成および使用する必要があります。ア
プリケーションは、認証と認可をサポートします。この2つは、Spring Framework 5.0によって提供されます。Spring
Framework 5.0は、Reactorと、Reactive Streams仕様に基づいて構築されており、2017年9月にリリースされまし
た。また、新たなリアクティブ・ランタイムと、Spring WebFluxと呼ばれるコンポーネント・モデルが含まれていま
す。Spring WebFluxの動作には、Servlet APIは必要ありません。サーブレット・エンジン上で動作できるようにす
るアダプタが同梱されているため、必要に応じてそのアダプタを使うこともできますが、必須ではありません。
ORACLE.COM/JAVAMAGAZINE ////////////////////// JANUARY/FEBRUARY 2018
53
//reactive programming /
また、NettyベースのWebサーバーも提供されています。Spring Framework 5は、Java 8とJava EE 7以上で動作
し、Springエコシステムの大半における変更の土台となっています。例を見てみます。
アプリケーションの例本を管理するサービスを表す、シンプルなSpring Boot 2.0アプリケーションを作ってみます。ここでは、Library
プロジェクトと呼ぶことにします。まず、Spring Initializrを開きます。バージョンを示すドロップダウン・メニューで
は、Spring Boot 2.0(またはそれ以降)を選択します。ここでは、図書館にある本へのアクセスを管理するサービス
を作成しようとしているため、このプロジェクトにはlibrary-serviceというアーティファクトIDを設定します。必要に
なる要素、Reactive Web、Actuator、Reactive MongoDB、Reactive Security、Lombokを選択します。
筆者が構築しているプロジェクトのほとんどはJavaによるものですが、よく使うのはKotlin言語です。ここ
では、KotlinプロジェクトにJavaアーティファクトを共存させることにします。「Generate Project」をクリックする
と、アーカイブがダウンロードされます。zipアーカイブを解凍し、Java 8(またはそれ以降)、Kotlin(オプション)
、Mavenをサポートしているお好みのIDEで開きます。Spring InitializrではGradleを選ぶこともできますが、本記事
では、Mavenを使用します。標準のSpring Bootアプリケーションには、リスト5のようなエントリ・クラスが含まれ
ています。
リスト5:新規Spring Bootプロジェクト用の空のクラスpackage com.example.libraryservice;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication public class LibraryServiceApplication { public static void main(String[] args) { System.setProperty("spring.profiles.active", "security,authorization,frpjava"); SpringApplication.run(LibraryServiceApplication.class, args); } }
ORACLE.COM/JAVAMAGAZINE ////////////////////// JANUARY/FEBRUARY 2018
54
//reactive programming /
リアクティブSpring Dataモジュールによるデータ・アクセス Spring Dataの最新リリースでは、対応している基盤データストア(MongoDB、Cassandra、Redis、Couchbaseな
ど)に対するリアクティブなデータ・アクセスが初めてサポートされています。また、このリリースでは、新しいリアク
ティブ・リポジトリとテンプレート実装も導入されています。クラスパスには、リアクティブなMongoDBドライバと
Spring Dataモジュールが存在するため、それらを使ってデータを管理してみます。リスト6に示すように、Bookという新しいエンティティを作成します。
リスト6:MongoDBの@DocumentエンティティであるBookpackage com.example.libraryservice;
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document;
@Document @Data @AllArgsConstructor @NoArgsConstructor public class Book { @Id private String id; private String title; private String author; }
次に、エンティティのデータ管理ライフサイクルをサポートするために、Spring Dataリポジトリを作成します。この
操作は、Spring Dataを使ったことがある方にはおなじみでしょう。ただし、リポジトリはリアクティブなインタラク
ションをサポートしているため、メソッドはPublisher型を返し、入力はPublisherインスタンスとして指定できま
ORACLE.COM/JAVAMAGAZINE ////////////////////// JANUARY/FEBRUARY 2018
55
//reactive programming /
す。リスト7をご覧ください。
リスト7:リアクティブなSpring Data MongoDBリポジトリpackage com.example.libraryservice; import org.springframework.data.mongodb.repository.ReactiveMongoRepository; import reactor.core.publisher.Flux;
public interface BookRepository extends ReactiveMongoRepository { Flux findByAuthor(String author); }
サンプル・データの登録ここまで来ると、いくつかのサンプル・データ(単なるデモ用のものです)を登録できます。アプリケーションが起
動すると、Spring Bootによって#run(ApplicationArguments)メソッドが呼び出されます。その際に渡された引
数(String [] args)をラップしたものがアプリケーションに渡されます。それでは、ApplicationRunnerを作成し
てみます。まず、データソースのすべてのデータを削除し、いくつかの本のタイトルを作成し、それらのタイトルを
Bookエンティティにマッピングしてから、それらの本を永続化します。最後に、データソースのすべてのレコードを
問い合わせて、すべての結果を表示します。以上のすべてを行うコードをリスト8に示します。
リスト8:データを書き込むApplicationRunnerpackage com.example.libraryservice;
import lombok.extern.slf4j.Slf4j; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component; import reactor.core.publisher.Flux;
@Slf4j
ORACLE.COM/JAVAMAGAZINE ////////////////////// JANUARY/FEBRUARY 2018
56
//reactive programming /
@Component
class SampleBookInitializer implements ApplicationRunner { private final BookRepository bookRepository; SampleBookInitializer(BookRepository bookRepository) { this.bookRepository = bookRepository; }
@Override public void run(ApplicationArguments args) throws Exception { this.bookRepository .deleteAll() .thenMany( Flux.just( "Cloud Native Java|jlong", "Spring Security 3.1|rwinch", "Spring in Action|cwalls")) .map(t -> t.split("\\|")) .map(tuple -> new Book(null, tuple[0], tuple[1])) .flatMap(this.bookRepository::save) .thenMany(this.bookRepository.findAll()) .subscribe(book -> log.info(book.toString())); } }
この例では、複数の本のタイトルと、それぞれの本の著者(多数存在する可能性もあります)のうち1名ずつを取得
して、それらをデータベースに書き込んでいます。まず、デリミタ「|」で文字列を分割しています。次に、本のタイトル
と本の著者を使用してBookを作成し、レコードをデータソースであるMongoDBに保存しています。保存操作の結
果は、Mono<Book>になります。結果のPublisher<T>インスタンスのそれぞれをサブスクライブする必要がある
ため、flatMap操作を使用しています。次に、すべてのレコードを検索して、調査のためにその結果を記録していま
す。
ORACLE.COM/JAVAMAGAZINE ////////////////////// JANUARY/FEBRUARY 2018
57
//reactive programming /
このコードでは、パイプラインを定義しています。それぞれの操作がパイプラインにおける1つのステージ
の定義です。パイプラインの実行は即時的実行ではありません。つまり、アクティブ化されるまで実行されませ
ん。パイプラインは、サブスクライブ(リスト8のコードの最終ステップ)することによってアクティブ化されま
す。Publisherで定義されているサブスクリプションは1種類だけですが、Reactorは発行されたそれぞれの値を処
理するためのフックを提供しています。このフックでは、スローされた任意の例外なども処理できます。
リスト8のいずれかのラムダ式にブレークポイントを指定してThread.currentThread().getName()の値を調べてみれば、処理が実行されているスレッドはメイン・スレッド(名前がmain)ではないことがわ
かります。Reactorの処理は、Schedulerの実装に従います。Schedulers.setFactory(Factory)を呼び出す
と、使いたいデフォルトのグローバルSchedulerを指定できます。Mono::subscribeOn(Scheduler)または
Flux::subscribeOn(Scheduler)を指定すると、Schedulerをサブスクライブする際に、特定のPublisherを実行す
るスレッドを指定できます。
まとめ本記事では、Spring BootとSpring Initializrを使い、リアクティブ開発の要件に厳密に従うリアクティブ・データ・ア
プリケーションを短時間で作成して実行しました。本記事のパート2(最終回)では、Spring Framework 5.0を使っ
てREST APIを立ち上げ、このデータへのセキュアなアクセスを実現したいと思います。それまでの間に完全なアプリ
ケーションを見ておきたいという方のために、ソース・コードをすべてオンラインで公開しています。</article>
Josh Long(@starbuxman):Java Champion。PivotalのSpringデベロッパー・アドボケート。Springプログラミングに関する複数の書籍を執筆しているほか、開発者会議で頻繁に講演を行っている。