リアクティブspring:rest api のセットアップ march/april 2018 72 //reactive programming...

12
ORACLE.COM/JAVAMAGAZINE ////////////////////////////// MARCH/APRIL 2018 70 //reactive programming / 誌の前号では、2 回シリーズの記事のパート1 をお届けしました。その中では、Spring Framework 5.0 で 利用できるリアクティブ・コンポーネントについて説明し、本のデータを提供する簡単なプロジェクトを構 築しました。これでデータソースにデータが準備できたことから、次は REST API を立ち上げます。ここでは、まっ たく新しいリアクティブ Web ランタイムであり、コンポーネント・モデルでもある Spring WebFlux を使います。 Spring WebFlux の動作には、Java のサーブレット仕様は必要ありません。Netty ベースの Web サーバーが搭載 されているため、独立して動作します。Spring WebFlux は、 Publisher<T> インスタンスを使って動作するように 最初から設計されています。 本記事の内容を理解するためには、少しばかり Spring の背景を知っている必要があります。しかし、本シリー ズの前回の記事を読めば、この点は十分カバーできるでしょう。 Spring WebFlux Spring WebFluxでは、 リスト1 に示すように、Spring のモデル・ビュー・コントローラ(MVC)スタイルのコントロー ラを使うことができます。 リスト 1: Spring の MVC スタイルの REST API package com.example.libraryservice; import org.springframework.context.annotation.Profile; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; リアクティブSpring:REST API のセットアップ Spring Frameworkを使ってリアクティブ・アプリを構築したパート1に続 き、短時間でREST APIを実装してアプリにアクセス JOSH LONG

Upload: lamanh

Post on 03-Mar-2019

218 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: リアクティブSpring:REST API のセットアップ MARCH/APRIL 2018 72 //reactive programming / 共存させることができ、それらを1つずつアクティブ化できることです。Spring

ORACLE.COM/JAVAMAGAZINE ////////////////////////////// MARCH/APRIL 2018

70

//reactive programming /

本誌の前号では、2 回シリーズの記事のパート1をお届けしました。その中では、Spring Framework 5.0で

利用できるリアクティブ・コンポーネントについて説明し、本のデータを提供する簡単なプロジェクトを構

築しました。これでデータソースにデータが準備できたことから、次はREST APIを立ち上げます。ここでは、まっ

たく新しいリアクティブ Webランタイムであり、コンポーネント・モデルでもあるSpring WebFluxを使います。

Spring WebFlux の動作には、Java のサーブレット仕様は必要ありません。Netty ベースのWebサーバーが搭載

されているため、独立して動作します。Spring WebFlux は、Publisher<T> インスタンスを使って動作するように

最初から設計されています。

本記事の内容を理解するためには、少しばかりSpringの背景を知っている必要があります。しかし、本シリー

ズの前回の記事を読めば、この点は十分カバーできるでしょう。

Spring WebFluxSpring WebFluxでは、リスト1に示すように、Spring のモデル・ビュー・コントローラ(MVC)スタイルのコントローラを使うことができます。

リスト1:Spring の MVC スタイルの REST APIpackage com.example.libraryservice;import org.springframework.context.annotation.Profile;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RestController;import reactor.core.publisher.Flux;

リアクティブSpring:REST APIのセットアップSpring Frameworkを使ってリアクティブ・アプリを構築したパート1に続き、短時間でREST APIを実装してアプリにアクセス

JOSH LONG

Page 2: リアクティブSpring:REST API のセットアップ MARCH/APRIL 2018 72 //reactive programming / 共存させることができ、それらを1つずつアクティブ化できることです。Spring

ORACLE.COM/JAVAMAGAZINE ////////////////////////////// MARCH/APRIL 2018

71

//reactive programming /

@Profile("mvc-style")@RestControllerclass BookRestController { private final BookRepository bookRepository; BookRestController(BookRepository bookRepository) { this.bookRepository = bookRepository; }

@GetMapping("/books") Flux<Book> all() { return this.bookRepository.findAll(); }

@GetMapping("/books/{author}") Flux<Book> byAuthor(@PathVariable String author) { return this.bookRepository.findByAuthor(author); }}

Spring には、プロファイルという考え方があります。プロファイルは、本質的に Spring Bean のラベル、すなわ

ちタグです。あるプロファイルの Bean は、そのプロファイルがアクティブ化されなければ存在しません。プロファ

イルをアクティブ化するもっとも簡単な方法は、java コマンドを実行する際にコマンドライン引数を使うことです。

たとえば、profile1 および profile2 というプロファイルに格納されているすべての Beanをアクティブ化する場合、

次のようなコマンドラインを使います。

java -Dspring.profiles.active=profile1,profile2 -jar ...

ここでのプロファイルのメリットは、3 種類の方法で実装した同じHTTPエンドポイントを同じコードベースの中に

Page 3: リアクティブSpring:REST API のセットアップ MARCH/APRIL 2018 72 //reactive programming / 共存させることができ、それらを1つずつアクティブ化できることです。Spring

ORACLE.COM/JAVAMAGAZINE ////////////////////////////// MARCH/APRIL 2018

72

//reactive programming /

共存させることができ、それらを1つずつアクティブ化できることです。Spring MVCを一度でも使ったことがある

方には、リスト1のコントローラはおなじみのものに見えるでしょうが、これは Spring MVC ではありません。ここで使っているのは、Spring WebFluxと呼ばれる新しいリアクティブ・ランタイムです。アノテーションは同じですが、

ルールは違うこともあります。

関数型リアクティブ・エンドポイントリスト1は、コントローラの実例です。Spring WebFlux のコントローラでは、宣言的アノテーションによって、エンドポイント・ハンドラとエンドポイント・マッピングを定義します。このアノテーションには、指定したエンド

ポイントへのルーティングをどのように処理するかを記述します。アノテーションは洗練されたものですが、究極

的には、フレームワーク自体がアノテーションに対して行えることしかできません。それよりも柔軟なリクエスト・

マッチング機能が必要な場合、リスト2に示すように、Spring WebFlux の関数型リアクティブ・エンドポイントを使うことができます。このコードは、frpjava プロファイルを使って実行できます。

リスト2:Java の関数型リアクティブ・エンドポイントとして作り直した、リスト1と同じエンドポイント

package com.example.libraryservice;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Profile;import org.springframework.web.reactive.function.server.RouterFunction;import static org.springframework.web.reactive.function.server.RequestPredicates.GET;import static org.springframework.web.reactive.function.server.RouterFunctions.route;import static org.springframework.web.reactive.function.server.ServerResponse.ok;

@Profile("frp-java")@Configuration

Page 4: リアクティブSpring:REST API のセットアップ MARCH/APRIL 2018 72 //reactive programming / 共存させることができ、それらを1つずつアクティブ化できることです。Spring

ORACLE.COM/JAVAMAGAZINE ////////////////////////////// MARCH/APRIL 2018

73

//reactive programming /

class BookRestConfigurationJava { @Bean

RouterFunction<?> routes(BookRepository br) { return route(GET("/books"), req -> ok().body(br.findAll(), Book.class)) .andRoute(GET("/books/{author}"), req -> ok().body(br.findByAuthor(req.pathVariable("author")), Book.class)); }}

関数型リアクティブ・スタイルを使うと、ハンドラ・クラスにマッピングされたリクエスト述語としてHTTPエンド

ポイントを表現できます。ハンドラ・クラスの実装は、簡潔な Java のラムダ式で容易に表現できます。デフォル

トのリクエスト述語を使うことも、独自の述語を指定してリクエストのマッチングやディスパッチの方法を完全に

制御することもできます。

リスト2では、生成した結果を、クラス・リテラルとともにbody(Publisher<T>) メソッドに渡しています。クラス・リテラルが必要なのは、どのようなタイプのメッセージを組み立てればよいかをエンジンが判断できるよ

うにするためです。Publisher<T> は、膨大な

数のレコードをプロデュースする可能性がある

ことを思い出してください。無限に続く可能

性すらあるため、プロデューサには、すべて

のレコードがプロデュースされるのを待ってか

ら、そのレコードをオブジェクトからJSON にマーシャルする余裕はありません。そのため、レコードを取得する

たびにマーシャルを行います。プロデューサには、探すメッセージの種類を伝える必要があります。Spring MVC

スタイルのコントローラでは、ハンドラ・メソッドの戻り値(Publisher<T>)がジェネリック・パラメータT をエンコー

ドします。エンジンはリフレクションを使ってそのジェネリック・パラメータを取得できます。しかし、エンジンは、

body メソッドにパラメータとして渡されたインスタンス変数に対してこの操作を行うことはできません。簡単にイ

ンスタンス変数のジェネリック・シグネチャを取得できる方法がないからです。この制限は、型消去と呼ばれてい

Spring Securityフレームワークには、あらゆる形式のIDプロバイダを組み込むことができる豊富な機能が搭載されています。

Page 5: リアクティブSpring:REST API のセットアップ MARCH/APRIL 2018 72 //reactive programming / 共存させることができ、それらを1つずつアクティブ化できることです。Spring

ORACLE.COM/JAVAMAGAZINE ////////////////////////////// MARCH/APRIL 2018

74

//reactive programming /

ます。型リテラルを使うと、この制限を回避できます。Kotlin言語を使っている方なら、Spring Framework 5に

付属するKotlin言語DSLを使用して、はるかに簡単にこの問題を解決できます。Kotlin DSLでは、インライン・メソッ

ドのランタイム・レイフィケーションのおかげで、必要なコード量が減るだけでなく、ジェネリック・パラメータを

取得することもできます。

リスト3に、同じエンドポイントをKotlin言語 DSLで実装し直したものを示します。

リスト3:Kotlin言語 DSLを使った同じエンドポイントpackage com.example.libraryserviceimport org.springframework.context.annotation.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.context.annotation.Profileimport org.springframework.web.reactive.function.server.ServerResponse.okimport org.springframework.web.reactive.function.server.bodyimport org.springframework.web.reactive.function.server.router

@Profile("frp-kotlin")@Configurationclass BookRestConfigurationKotlin { @Bean fun routes(br:BookRepository) = router { GET("/books") { r -> ok().body(br.findAll()) } GET("/books/{author}") { r -> ok().body(br.findByAuthor(r.pathVariable("author"))) } }}

Spring Securityしかし、このコードでもまだ本番環境で使用できる状態ではありません。次に行う必要があるのは、セキュリティ

Page 6: リアクティブSpring:REST API のセットアップ MARCH/APRIL 2018 72 //reactive programming / 共存させることができ、それらを1つずつアクティブ化できることです。Spring

ORACLE.COM/JAVAMAGAZINE ////////////////////////////// MARCH/APRIL 2018

75

//reactive programming /

への対処です。Spring Securityフレームワークには、あらゆる形式の IDプロバイダを組み込むことができる豊富

な機能が搭載されています。このフレームワークは、アプリケーションレベルのコード(メソッド呼出し、HTTPリ

クエストなど)から簡単にアクセスできるように、セキュリティ・コンテキストを伝播させることで認証をサポート

しています。従来より、セキュリティ・コンテキストはThreadLocal で実装されています。リアクティブの世界では、

スレッドローカルな状態はあまり意味をなしません。前回の記事で詳しく説明した Spring Reactor は、ディクショ

ナリのような動作をするContext オブジェクトを提供します。Spring Security 5.0 のリアクティブ・サポートでは、

このメカニズムによってセキュリティ・コンテキストが伝播します。また、ノンブロッキングな認証および認可をサ

ポートするため、並列なリアクティブ型の階層が導入されています。この点についてあまり心配する必要はなく、知っ

ておくべきことは1つだけです。リアクティブの世界では、ReactiveAuthenticationManager という型のオブジェ

クトが認証処理を行います。このオブジェクトの仕事は単純で、指定された Authentication を試行し、その認

証試行が成功したかどうかを示すMono<Authentication> を返します。成功しなかった場合は、例外がスロー

されます。

ReactiveAuthenticationManagerの実装の1つに、ユーザーが提供するMapReactiveUserDetailsService型オブジェクトへの委譲に対応したものがあります。MapReactiveUserDetailsService は、カスタムのユーザー

名とパスワードのストアをSpring Security の認証に接続します。USERS というデータベース表か、またはユーザー

をハードコードしたMap<K,V> だけがあるかもしれません。デフォルトで、Spring Security は、HTTP Basic 認

証を追加してアプリケーション全体をロックします。資格証明を提供しなければ、どのエンドポイントをどのよう

に呼び出そうとしても失敗します。デフォルトでは、すべての認証されたプリンシパルがすべてのエンドポイント

にアクセスすることができます。

それでは、さまざまなロールを持つ何名かのユーザーを追加してみます。USER ロールはすべてのユーザーに

持たせますが、ADMIN ロールは一部の特権ユーザーのみに与えます。今回新しく導入するセキュリティでは、す

べてのユーザーは自分の書いた本を参照できますが、すべての本を参照できるのはADMIN ロールを持つユーザー

だけとします。リスト4に、これを実現する方法を示します(この権限が実際に意味をなすかどうかは、ひとまず横に置いておきます)。

リスト4:Spring Security 構成へのユーザーの追加package com.example.libraryservice;import org.springframework...

Page 7: リアクティブSpring:REST API のセットアップ MARCH/APRIL 2018 72 //reactive programming / 共存させることができ、それらを1つずつアクティブ化できることです。Spring

ORACLE.COM/JAVAMAGAZINE ////////////////////////////// MARCH/APRIL 2018

76

//reactive programming /

@Profile("security")@Configuration@EnableWebFluxSecurityclass SecurityConfiguration { @Bean ReactiveUserDetailsService authentication() { User.UserBuilder builder = User.withDefaultPasswordEncoder(); return new MapReactiveUserDetailsService( builder.username("rjohnson") .password("pw").roles("ADMIN").build(),

builder.username("cwalls") .password("pw").roles().build(),

builder.username("jlong") .password("pw").roles().build(),

builder.username("rwinch") .password("pw").roles("ADMIN").build()); }

@Bean @Profile("authorization") SecurityWebFilterChain authorization(ServerHttpSecurity http) { ReactiveAuthorizationManager<AuthorizationContext> am = (auth, ctx) ->auth.map(authentication -> { Object author = ctx.getVariables().get("author"); boolean matchesAuthor = authentication.getName().equals(author); boolean isAdmin =

Page 8: リアクティブSpring:REST API のセットアップ MARCH/APRIL 2018 72 //reactive programming / 共存させることができ、それらを1つずつアクティブ化できることです。Spring

ORACLE.COM/JAVAMAGAZINE ////////////////////////////// MARCH/APRIL 2018

77

//reactive programming /

authentication .getAuthorities() .stream() .anyMatch(ga -> ga.getAuthority().contains("ROLE_ADMIN")); return (matchesAuthor || isAdmin); }) .map(AuthorizationDecision::new);

return http.httpBasic() .and() .authorizeExchange() .pathMatchers("/books/{author}").access(am) .anyExchange().hasRole("ADMIN") .and() .build(); } }

このコードでは、認証と認可に使用するいくつかのルールを設定しています。Spring Security は、任意の数の、

異なる IDプロバイダと通信できますが、この例では、ユーザー名、パスワード、関連付けられたロールをハードコー

ドしたマップを使っています。ReactiveUserDetailsService Bean は、ユーザー名とパスワードに基づいた認証処

理を行います。

認可を行うBean は、ServerHttpSecurity ビルダーDSLを使って、/books/{author} エンドポイントに対する

リクエストを除くすべてのリクエストにADMIN ロールが必要であることを明示しています。今回は、あるカスタ

ムのビジネス・ロジック(ReactiveAuthorizationManager に記述されています)に従っています。具体的には、

リクエストのパス変数を調べ、パス変数内の author が現在認証されているユーザーと一致する場合、または現

在認証されているプリンシパルが管理者である場合(すなわち、ROLE_ADMIN ロールを持っている場合)にのみ、

リクエストの継続を許可します。

Page 9: リアクティブSpring:REST API のセットアップ MARCH/APRIL 2018 72 //reactive programming / 共存させることができ、それらを1つずつアクティブ化できることです。Spring

ORACLE.COM/JAVAMAGAZINE ////////////////////////////// MARCH/APRIL 2018

78

//reactive programming /

リスト5では、HTTP Basic により認証された状態でサービスを呼び出しています。ここでは、curl を使って、一般 USER である jlong として最初のリクエストを行っています。

リスト5:curl によりjlongとして行う、エンドポイントへのアクセスcurl -ujlong:pw http://localhost:8080/books/jlong

リスト6のようにしてhttp://localhost:8080/books/rwinch にアクセスしようとした場合は、失敗します。他のエンドポイントには、ADMIN ロールを持つユーザーだけがアクセスできます。

リスト6:curl によりrwinchとして行う、エンドポイントへのアクセスcurl -urwinch:pw http://localhost:8080/books

デプロイメントアプリケーションは保護され、監視可能になったため、ようやくデプロイすることができます。こういった種類の

アプリは、クラウド・プロバイダで実行するのが自然です。たとえば、Apache License バージョン2.0 によりリリー

スされているオープンソース・クラウド・プラットフォームで、アプリケーションを継続的に管理するために最適化

されているCloud Foundryなどが考えられます。Cloud Foundry は、クラウド・インフラストラクチャの1段階(ま

たは 2 段階)上に位置するものです。インフラストラクチャに依存しないことから、OpenStack や vSphereな

どのローカル・クラウド・プロバイダでも、Amazon Web Services、Google Cloud、Microsoft Azure、Oracle

Cloudなどのパブリック・クラウド・プロバイダでも動作します。Cloud Foundryがどこにインストールされていても、

使用方法は基本的に同じです。具体的には、認証した後、cf コマンドライン・インタフェース(CLI)を使ってプラッ

トフォームにアプリケーションのワークロードを伝え、cf push コマンドを実行します。リスト7に例を示します。

リスト7:cf CLI による、アプリケーションのプッシュcf login -a $CF_API_ENDPOINT -u $CF_USER -s $CF_SPACE -o $CF_ORGcf push -p library-service-0.0.1-SNAPSHOT.jar java-magazine-library-service

アプリケーションが起動して実行されると、パブリックHTTPエンドポイントにアクセスできるようになります。cf create-

Page 10: リアクティブSpring:REST API のセットアップ MARCH/APRIL 2018 72 //reactive programming / 共存させることができ、それらを1つずつアクティブ化できることです。Spring

ORACLE.COM/JAVAMAGAZINE ////////////////////////////// MARCH/APRIL 2018

79

//reactive programming /

service を使うと、メッセージ・キュー、データベース、キャッシュなどのバックエンド・サービスをプロビジョニ

ングすることができます。cf scale を使うと、負荷分散された複数のインスタンスにアプリケーションをスケール

アップすることもできます。Pivotal Apps Managerダッシュボードから、アプリケーションの指標、Spring Boot

Actuatorエンドポイント、健全性などを確認することもできます。ここまでで、アプリケーションが起動して実行

され、クライアントとセキュアな形で通信できるようになりました。次は、クライアントに目を向けてみます。

(リアクティブ)クライアントREST APIを立ち上げることはできました。クライアントを、そのサービスに接続する必要があります。ここ10

年の大半にわたって開発者たちをサポートしてきた、強力な汎用 HTTPクライアントであるSpring Framework

RestTemplate を使うこともできますが、これは無限に続く可能性があるデータ・ストリームに特化しているわけ

ではありません。RestTemplate は、ドキュメントまたはファイルの最後まで読み取り、ペイロード全体を取得し

てから、一括で変換を行います。クライアントがServer-Sent Events(SSE)を使っている場合や、さらには、巨

大な JSONレスポンスを扱うだけでも、うまく動作しません。そこで代わりに、リスト8に示すように、新しいSpring WebFlux WebClient を使うことにします。

リスト8:認証された WebClientの構成と使用package com.example.libraryclient;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import org.springframework...

@SpringBootApplicationpublic class LibraryClientApplication {

@Bean WebClient client( @Value("${libary-service-url:http://localhost:8080/}") String url) { ExchangeFilterFunction basicAuth = ExchangeFilterFunctions

Page 11: リアクティブSpring:REST API のセットアップ MARCH/APRIL 2018 72 //reactive programming / 共存させることができ、それらを1つずつアクティブ化できることです。Spring

ORACLE.COM/JAVAMAGAZINE ////////////////////////////// MARCH/APRIL 2018

80

//reactive programming /

.basicAuthentication("rwinch", "pw"); return WebClient .builder() .baseUrl(url) .filter(basicAuth)

.build(); } @Bean ApplicationRunner run(WebClient client) { return args -> client.get().uri("/books") .retrieve() .bodyToFlux(Book.class) .subscribe(System.out::println); }

public static void main(String[] args) { SpringApplication.run(LibraryClientApplication.class, args); }}

@Data@AllArgsConstructor@NoArgsConstructorclass Book { private String id; private String title; private String author;}

リスト8のコードでは、WebClient を構成し、baseUrl と、サービスでクライアントを認証するExchangeFilterFunction をあらかじめ設定しています。ここでは、WebClient からレスポンスとして得られる

Publisher<T> をコンソールに出力しています。クライアントは別のプロセスで動作することから、Book クラスの

Page 12: リアクティブSpring:REST API のセットアップ MARCH/APRIL 2018 72 //reactive programming / 共存させることができ、それらを1つずつアクティブ化できることです。Spring

ORACLE.COM/JAVAMAGAZINE ////////////////////////////// MARCH/APRIL 2018

81

//reactive programming /

定義をここにも記載します。そうすることによって、WebClient が Book クラスに JSONをバインドし、クライアン

ト側データ転送オブジェクトとして扱うことができるようになります。この例では、その点はあまり重要ではあり

ません。エンドポイントに4 件しかレコードがないためです。このWebクライアントは、無限に続く可能性があ

るデータを扱えるように設計されています。このコードは、4 件のレコードを処理するために見てきましたが、無

限の量のデータを処理できます。

まとめこの2 回シリーズの記事では、Spring Bootで Webサービスを構築する方法について簡単に説明しました。その

中で取り上げたのは、Reactor、Spring Data Kay、Spring Framework 5、Spring WebFlux、Spring Security 5、

Spring Boot 2です。Spring Boot 2を使うと、さまざまなリアクティブ Springプロジェクトを組み合わせて、簡単

に1つのアプリケーションを作ることができます。Spring Boot Actuator については説明しませんでしたが、これ

は指標やアプリケーションの健全性などの運用データを取得するものです。Spring Boot Actuator も、リアクティ

ブな世界とシームレスに連携するようにアップデートされています。

Spring Boot 2は、今後リリースされる予定となっているSpring Cloud Finchley への布石となるものです。

Spring Cloud Finchley は、Spring Boot 2.0上に構築されており、Spring WebFluxベースのアプリケーションで

のリアクティブ・プログラミング、サービス登録、検出作業をサポートするために、いくつかのAPIをアップデー

トしたものです。Spring Cloud Commons は、Spring Framework WebClient のサービス・レジストリ(Apache

Zookeeper、HashiCorp Consul、Netflix Eureka など)に登録されているサービス間のロードバランシングをクラ

イアント側でサポートします。Spring Cloud Netflix Hystrix のサーキット・ブレーカは、以前からRxJavaと親和

性が高く、Spring の Publisher<T> インスタンスとの相互運用が可能になっています。

Spring Cloud Stream は、メッセージの受信方法や、RabbitMQ、Apache Kafka、Redis などのメッセージング・

レイヤーへの送信方法を記述することで、Publisher<T> インスタンスとの連携をサポートします。

ぜひ、Spring Initializrを使って、Spring Bootでリアクティブなアプリケーションやサービスを構築する旅を

始めてみてください。本記事で紹介したコードの完全版はGitHubに掲載されています。質問がある方は、Twitter(@

starbuxman)または電子メール([email protected])でご連絡ください。</article>

Josh Long(@starbuxman):Java Champion。Pivotal の Springデベロッパー・アドボケート。Springプログラミングに関する複数の書籍を執筆しているほか、開発者会議で頻繁に講演を行っている。