最新の安定バージョンについては、Spring GraphQL 1.3.1 を使用してください!

クライアント

Spring for GraphQL には、HTTP、WebSocket、RSocket を介して GraphQL リクエストを実行するためのクライアントサポートが含まれています。

GraphQlClient

GraphQlClient は、基になるトランスポートから独立した GraphQL リクエストの共通ワークフローを宣言する契約です。つまり、基になるトランスポートが何であっても、リクエストは同じ API で実行され、トランスポート固有のものはビルド時に構成されます。

GraphQlClient を作成するには、次の拡張のいずれかが必要です。

それぞれが、トランスポートに関連するオプションを持つ Builder を定義します。すべてのビルダーは、共通のベース GraphQlClient Builder から拡張され、すべての拡張に関連するオプションがあります。

GraphQlClient を取得したら、リクエストを開始できます。

HTTP

HttpGraphQlClient は WebClient を使用して、HTTP 経由で GraphQL リクエストを実行します。

WebClient webClient = ... ;
HttpGraphQlClient graphQlClient = HttpGraphQlClient.create(webClient);

HttpGraphQlClient が作成されると、基礎となるトランスポートとは関係なく、同じ API を使用してリクエストの実行を開始できます。トランスポート固有の詳細を変更する必要がある場合は、既存の HttpGraphQlClient で mutate() を使用して、カスタマイズされた設定で新しいインスタンスを作成します。

   WebClient webClient = ... ;

HttpGraphQlClient graphQlClient = HttpGraphQlClient.builder(webClient)
		.headers(headers -> headers.setBasicAuth("joe", "..."))
		.build();

// Perform requests with graphQlClient...

HttpGraphQlClient anotherGraphQlClient = graphQlClient.mutate()
		.headers(headers -> headers.setBasicAuth("peter", "..."))
		.build();

// Perform requests with anotherGraphQlClient...

WebSocket

WebSocketGraphQlClient は、共有 WebSocket 接続を介して GraphQL リクエストを実行します。これは Spring WebFluxWebSocketClient を使用して構築されており、次のように作成できます。

String url = "wss://localhost:8080/graphql";
WebSocketClient client = new ReactorNettyWebSocketClient();

WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client).build();

HttpGraphQlClient とは対照的に、WebSocketGraphQlClient は接続指向です。つまり、リクエストを行う前に接続を確立する必要があります。リクエストを開始すると、接続が透過的に確立されます。または、クライアントの start() メソッドを使用して、リクエストの前に明示的に接続を確立します。

接続指向であることに加えて、WebSocketGraphQlClient は多重化もされています。すべてのリクエストに対して単一の共有接続を維持します。接続が失われた場合は、次のリクエストで、または start() が再度呼び出された場合に再確立されます。進行中のリクエストをキャンセルし、接続を閉じ、新しいリクエストを拒否するクライアントの stop() メソッドを使用することもできます。

そのサーバーへのすべてのリクエストに対して単一の共有接続を確立するには、サーバーごとに単一の WebSocketGraphQlClient インスタンスを使用します。各クライアントインスタンスは独自の接続を確立しますが、これは通常、単一のサーバーを対象とするものではありません。

WebSocketGraphQlClient が作成されると、基礎となるトランスポートとは関係なく、同じ API を使用してリクエストの実行を開始できます。トランスポート固有の詳細を変更する必要がある場合は、既存の WebSocketGraphQlClient で mutate() を使用して、カスタマイズされた設定で新しいインスタンスを作成します。

URI url = ... ;
WebSocketClient client = ... ;

WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client)
		.headers(headers -> headers.setBasicAuth("joe", "..."))
		.build();

// Use graphQlClient...

WebSocketGraphQlClient anotherGraphQlClient = graphQlClient.mutate()
		.headers(headers -> headers.setBasicAuth("peter", "..."))
		.build();

// Use anotherGraphQlClient...

インターセプター

WebSocket 上の GraphQL [GitHub] (英語) プロトコルは、実行リクエストに加えて、多数の接続指向メッセージを定義します。例: クライアントは "connection_init" を送信し、サーバーは接続の開始時に "connection_ack" で応答します。

WebSocket トランスポート固有のインターセプトの場合、WebSocketGraphQlClientInterceptor を作成できます。

static class MyInterceptor implements WebSocketGraphQlClientInterceptor {

	@Override
	public Mono<Object> connectionInitPayload() {
		// ... the "connection_init" payload to send
	}

	@Override
	public Mono<Void> handleConnectionAck(Map<String, Object> ackPayload) {
		// ... the "connection_ack" payload received
	}

}

上記のインターセプターを他の GraphQlClientInterceptor と同様に登録し、それを GraphQL リクエストのインターセプトにも使用しますが、型 WebSocketGraphQlClientInterceptor のインターセプターは最大で 1 つしか存在できないことに注意してください。

RSocket

RSocketGraphQlClient は RSocketRequester を使用して、RSocket リクエストを介して GraphQL リクエストを実行します。

URI uri = URI.create("wss://localhost:8080/rsocket");
WebsocketClientTransport transport = WebsocketClientTransport.create(url);

RSocketGraphQlClient client = RSocketGraphQlClient.builder()
		.clientTransport(transport)
		.build();

HttpGraphQlClient とは対照的に、RSocketGraphQlClient は接続指向です。つまり、リクエストを行う前にセッションを確立する必要があります。リクエストを開始すると、セッションは透過的に確立されます。または、クライアントの start() メソッドを使用して、リクエストの前に明示的にセッションを確立します。

RSocketGraphQlClient も多重化されています。すべてのリクエストに対して単一の共有セッションを維持します。セッションが失われた場合、次のリクエストで、start() が再度呼び出された場合に再確立されます。進行中のリクエストをキャンセルし、セッションを閉じ、新しいリクエストを拒否するクライアントの stop() メソッドを使用することもできます。

そのサーバーへのすべてのリクエストに対して単一の共有セッションを使用するには、サーバーごとに単一の RSocketGraphQlClient インスタンスを使用します。各クライアントインスタンスは独自の接続を確立しますが、これは通常、単一のサーバーを対象とするものではありません。

RSocketGraphQlClient が作成されると、基礎となるトランスポートとは関係なく、同じ API を使用してリクエストの実行を開始できます。

ビルダー

GraphQlClient は、すべての拡張機能のビルダーに共通の構成オプションを持つ親 Builder を定義します。現在、以下を構成できます。

  • ファイルからリクエストのドキュメントをロードする DocumentSource 戦略

  • 実行されたリクエストのインターセプト

要求

GraphQlClient を取得したら、retrieve() または execute() を介してリクエストの実行を開始できます。前者は後者のショートカットにすぎません。

取得

以下は、クエリのデータを取得してデコードします。

String document = "{" +
		"  project(slug:\"spring-framework\") {" +
		"	name" +
		"	releases {" +
		"	  version" +
		"	}"+
		"  }" +
		"}";

Mono<Project> projectMono = graphQlClient.document(document) (1)
		.retrieve("project") (2)
		.toEntity(Project.class); (3)
1 実行する操作。
2 デコード元のレスポンスマップの「データ」キーのパス。
3 ターゲット型へのパスでデータをデコードします。

入力ドキュメントは String であり、リテラルまたはコード生成されたリクエストオブジェクトによって生成されます。ファイルでドキュメントを定義し、ドキュメントソースを使用してファイル名でドキュメントを再発行することもできます。

パスは「データ」キーに相対的であり、ネストされたフィールドには単純なドット ( "." ) で区切られた表記法を使用し、リスト要素のオプションの配列インデックスを使用します。"project.name" または "project.releases[0].version"

指定されたパスが存在しない場合、またはフィールド値が null でエラーがある場合、デコードすると FieldAccessException になる可能性があります。FieldAccessException は、レスポンスとフィールドへのアクセスを提供します。

Mono<Project> projectMono = graphQlClient.document(document)
		.retrieve("project")
		.toEntity(Project.class)
		.onErrorResume(FieldAccessException.class, ex -> {
			ClientGraphQlResponse response = ex.getResponse();
			// ...
			ClientResponseField field = ex.getField();
			// ...
		});

実行

取得は、レスポンスマップ内の単一のパスからデコードするためのショートカットにすぎません。さらに制御するには、execute メソッドを使用してレスポンスを処理します。

例:

Mono<Project> projectMono = graphQlClient.document(document)
		.execute()
		.map(response -> {
			if (!response.isValid()) {
				// Request failure... (1)
			}

			ClientResponseField field = response.field("project");
			if (!field.hasValue()) {
				if (field.getError() != null) {
					// Field failure... (2)
				}
				else {
					// Optional field set to null... (3)
				}
			}

			return field.toEntity(Project.class); (4)
		});
1 レスポンスにはデータがなく、エラーのみがあります
2null で、関連するエラーがあるフィールド
3DataFetcher によって null に設定されたフィールド
4 指定されたパスでデータをデコードします

ドキュメントソース

リクエストのドキュメントは String であり、ローカル変数または定数で定義されるか、コード生成されたリクエストオブジェクトによって生成されます。

クラスパス上の "graphql-documents/" に、拡張子 .graphql または .gql のドキュメントファイルを作成し、ファイル名で参照することもできます。

例: src/main/resources/graphql-documents 内の projectReleases.graphql というファイルが与えられた場合、内容は次のとおりです。

src/main/resources/graphql-documents/projectReleases.graphql
query projectReleases($slug: ID!) {
	project(slug: $slug) {
		name
		releases {
			version
		}
	}
}

その後、次のことができます。

Mono<Project> projectMono = graphQlClient.documentName("projectReleases") (1)
		.variable("slug", "spring-framework") (2)
		.retrieve()
		.toEntity(Project.class);
1 "projectReleases.graphql" からドキュメントを読み込みます
2 変数値を提供します。

IntelliJ の "JS GraphQL" プラグインは、コード補完 で GraphQL クエリファイルをサポートします。

GraphQlClient  ビルダーを使用して、名前でドキュメントをロードするために DocumentSource をカスタマイズできます。

サブスクリプションリクエスト

GraphQlClient は、それをサポートするトランスポート上でサブスクリプションを実行できます。WebSocket および RSocket トランスポートのみが GraphQL サブスクリプションをサポートしているため、WebSocketGraphQlClient または RSocketGraphQlClient を作成する必要があります。

取得

サブスクリプションストリームを開始するには、retrieveSubscription を使用します。これは、単一のレスポンスを取得するのに似ていますが、それぞれが何らかのデータにデコードされたレスポンスのストリームを返します。

Flux<String> greetingFlux = client.document("subscription { greetings }")
		.retrieveSubscription("greeting")
		.toEntity(String.class);

サブスクリプションがサーバー側から「エラー」メッセージで終了した場合、Flux は SubscriptionErrorException で終了する可能性があります。この例外により、「エラー」メッセージからデコードされた GraphQL エラーへのアクセスが提供されます。

基礎となる接続が閉じられるか失われた場合、Flux は WebSocketDisconnectedException などの GraphQlTransportException で終了することがあります。その場合、retry オペレーターを使用してサブスクリプションを再開できます。

クライアント側からサブスクリプションを終了するには、Flux をキャンセルする必要があり、次に WebSocket トランスポートが「完了」メッセージをサーバーに送信します。Flux の解約方法は使い方によって異なります。take や timeout などの一部のオペレーターは、それ自体で Flux をキャンセルします。Subscriber とともに Flux をサブスクライブしている場合は、Subscription への参照を取得し、それを通じてキャンセルできます。onSubscribe オペレーターは、Subscription へのアクセスも提供します。

実行

取得は、各レスポンスマップ内の単一のパスからデコードするためのショートカットにすぎません。さらに制御するには、executeSubscription メソッドを使用して、各レスポンスを直接処理します。

Flux<String> greetingFlux = client.document("subscription { greetings }")
		.executeSubscription()
		.map(response -> {
			if (!response.isValid()) {
				// Request failure...
			}

			ClientResponseField field = response.field("project");
			if (!field.hasValue()) {
				if (field.getError() != null) {
					// Field failure...
				}
				else {
					// Optional field set to null... (3)
				}
			}

			return field.toEntity(String.class)
		});

インターセプト

クライアントを介してすべてのリクエストをインターセプトする GraphQlClientInterceptor を作成します。

static class MyInterceptor implements GraphQlClientInterceptor {

	@Override
	public Mono<ClientGraphQlResponse> intercept(ClientGraphQlRequest request, Chain chain) {
		// ...
		return chain.next(request);
	}

	@Override
	public Flux<ClientGraphQlResponse> interceptSubscription(ClientGraphQlRequest request, SubscriptionChain chain) {
		// ...
		return chain.next(request);
	}

}

インターセプターが作成されたら、クライアントビルダーを使用して登録します。

URI url = ... ;
WebSocketClient client = ... ;

WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client)
		.interceptor(new MyInterceptor())
		.build();