クライアント
Spring for GraphQL には、HTTP、WebSocket、RSocket 経由で GraphQL リクエストを実行するためのクライアントサポートが含まれています。
GraphQlClient
GraphQlClient
は、基になるトランスポートとは独立した GraphQL リクエストの共通ワークフローを定義するため、どのトランスポートが使用されているかに関係なく、リクエストを実行する方法は同じです。
次のトランスポート固有の GraphQlClient
拡張機能が使用可能です。
それぞれが、トランスポートに関連するオプションを備えた Builder
を定義します。すべてのビルダーは、すべてのトランスポートに適用できるオプションを備えた共通のベース GraphQlClient Builder
から拡張されています。
GraphQlClient
が構築されたら、リクエストの作成を開始できます。
通常、リクエストの GraphQL オペレーションはテキストとして提供されます。あるいは、上記の GraphQlClient
拡張機能のいずれかをラップできる DgsGraphQlClient を通じて DGS コードジェネ [GitHub] (英語) クライアント API クラスを使用することもできます。
HTTP 同期
HttpSyncGraphQlClient
は、RestClient を使用して、ブロッキングトランスポート契約とインターセプターの チェーンを通じて HTTP 経由で GraphQL リクエストを実行します。
RestClient restClient = RestClient.create("https://spring.io/graphql");
HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.create(restClient);
HttpSyncGraphQlClient
が作成されると、基礎となるトランスポートとは関係なく、同じ API を使用してリクエストの実行を開始できます。トランスポート固有の詳細を変更する必要がある場合は、既存の HttpSyncGraphQlClient
で mutate()
を使用して、カスタマイズされた設定で新しいインスタンスを作成します。
RestClient restClient = RestClient.create("https://spring.io/graphql");
HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.builder(restClient)
.headers((headers) -> headers.setBasicAuth("joe", "..."))
.build();
// Perform requests with graphQlClient...
HttpSyncGraphQlClient anotherGraphQlClient = graphQlClient.mutate()
.headers((headers) -> headers.setBasicAuth("peter", "..."))
.build();
// Perform requests with anotherGraphQlClient...
HTTP
HttpGraphQlClient
は、WebClient を使用して、ノンブロッキングトランスポート契約とインターセプターの チェーンを通じて HTTP 経由で GraphQL リクエストを実行します。
WebClient webClient = WebClient.create("https://spring.io/graphql");
HttpGraphQlClient graphQlClient = HttpGraphQlClient.create(webClient);
HttpGraphQlClient
が作成されると、基礎となるトランスポートとは関係なく、同じ API を使用してリクエストの実行を開始できます。トランスポート固有の詳細を変更する必要がある場合は、既存の HttpGraphQlClient
で mutate()
を使用して、カスタマイズされた設定で新しいインスタンスを作成します。
WebClient webClient = WebClient.create("https://spring.io/graphql");
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 WebFlux の WebSocketClient を使用して構築されており、次のように作成できます。
String url = "wss://spring.io/graphql";
WebSocketClient client = new ReactorNettyWebSocketClient();
WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client).build();
HttpGraphQlClient
とは対照的に、WebSocketGraphQlClient
は接続指向です。つまり、リクエストを行う前に接続を確立する必要があります。リクエストを開始すると、接続が透過的に確立されます。または、クライアントの start()
メソッドを使用して、リクエストの前に明示的に接続を確立します。
接続指向であることに加えて、WebSocketGraphQlClient
は多重化もされています。すべてのリクエストに対して単一の共有接続を維持します。接続が失われた場合は、次のリクエストで、または start()
が再度呼び出された場合に再確立されます。進行中のリクエストをキャンセルし、接続を閉じ、新しいリクエストを拒否するクライアントの stop()
メソッドを使用することもできます。
そのサーバーへのすべてのリクエストに対して単一の共有接続を確立するには、サーバーごとに単一の WebSocketGraphQlClient インスタンスを使用します。各クライアントインスタンスは独自の接続を確立しますが、これは通常、単一のサーバーを対象とするものではありません。 |
WebSocketGraphQlClient
が作成されると、基礎となるトランスポートとは関係なく、同じ API を使用してリクエストの実行を開始できます。トランスポート固有の詳細を変更する必要がある場合は、既存の WebSocketGraphQlClient
で mutate()
を使用して、カスタマイズされた設定で新しいインスタンスを作成します。
String url = "wss://spring.io/graphql";
WebSocketClient client = new ReactorNettyWebSocketClient();
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...
WebSocketGraphQlClient
は、他のメッセージが送受信されていないときに接続をアクティブに保つために、定期的な ping メッセージの送信をサポートしています。これを有効にするには、次の手順を実行します。
String url = "wss://spring.io/graphql";
WebSocketClient client = new ReactorNettyWebSocketClient();
WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client)
.keepAlive(Duration.ofSeconds(30))
.build();
インターセプター
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(uri);
RSocketGraphQlClient client = RSocketGraphQlClient.builder()
.clientTransport(transport)
.build();
HttpGraphQlClient
とは対照的に、RSocketGraphQlClient
は接続指向です。つまり、リクエストを行う前にセッションを確立する必要があります。リクエストを開始すると、セッションは透過的に確立されます。または、クライアントの start()
メソッドを使用して、リクエストの前に明示的にセッションを確立します。
RSocketGraphQlClient
も多重化されています。すべてのリクエストに対して単一の共有セッションを維持します。セッションが失われた場合、次のリクエストで、start()
が再度呼び出された場合に再確立されます。進行中のリクエストをキャンセルし、セッションを閉じ、新しいリクエストを拒否するクライアントの stop()
メソッドを使用することもできます。
そのサーバーへのすべてのリクエストに対して単一の共有セッションを使用するには、サーバーごとに単一の RSocketGraphQlClient インスタンスを使用します。各クライアントインスタンスは独自の接続を確立しますが、これは通常、単一のサーバーを対象とするものではありません。 |
RSocketGraphQlClient
が作成されると、基礎となるトランスポートとは関係なく、同じ API を使用してリクエストの実行を開始できます。
ビルダー
GraphQlClient
は、すべての拡張機能のビルダーに共通の構成オプションを持つ親 BaseBuilder
を定義します。現在、以下を構成できます。
ファイルからリクエストのドキュメントをロードする
DocumentSource
戦略実行されたリクエストのインターセプト
BaseBuilder
は次のようにさらに拡張されています。
SyncBuilder
-SyncGraphQlInterceptor
の チェーンで実行スタックをブロックしています。Builder
-GraphQlInterceptor
の チェーンを使用したノンブロッキング実行スタック。
要求
GraphQlClient
を取得すると、取得メソッドまたは実行メソッドを介してリクエストの実行を開始できます。
取得
以下は、クエリのデータを取得してデコードします。
同期化
ノンブロッキング
String document =
"""
{
project(slug:"spring-framework") {
name
releases {
version
}
}
}
""";
Project project = graphQlClient.document(document) (1)
.retrieveSync("project") (2)
.toEntity(Project.class); (3)
String document =
"""
{
project(slug:"spring-framework") {
name
releases {
version
}
}
}
""";
Mono<Project> project = graphQlClient.document(document) (1)
.retrieve("project") (2)
.toEntity(Project.class); (3)
1 | 実行する操作。 |
2 | デコード元のレスポンスマップの「データ」キーのパス。 |
3 | ターゲット型へのパスでデータをデコードします。 |
入力ドキュメントは String
であり、リテラルまたはコード生成されたリクエストオブジェクトによって生成されます。ファイルでドキュメントを定義し、ドキュメントソースを使用してファイル名でドキュメントを再発行することもできます。
パスは「データ」キーに相対的であり、ネストされたフィールドには単純なドット ( "." ) で区切られた表記法を使用し、リスト要素のオプションの配列インデックスを使用します。"project.name"
または "project.releases[0].version"
。
指定されたパスが存在しない場合、またはフィールド値が null
でエラーがある場合、デコードすると FieldAccessException
になる可能性があります。FieldAccessException
は、レスポンスとフィールドへのアクセスを提供します。
同期化
ノンブロッキング
try {
Project project = graphQlClient.document(document)
.retrieveSync("project")
.toEntity(Project.class);
return project;
}
catch (FieldAccessException ex) {
ClientGraphQlResponse response = ex.getResponse();
// ...
ClientResponseField field = ex.getField();
// return fallback value
return new Project();
}
Mono<Project> projectMono = graphQlClient.document(document)
.retrieve("project")
.toEntity(Project.class)
.onErrorResume(FieldAccessException.class, (ex) -> {
ClientGraphQlResponse response = ex.getResponse();
// ...
ClientResponseField field = ex.getField();
// return fallback value
return Mono.just(new Project());
});
実行
取得は、レスポンスマップ内の単一のパスからデコードするためのショートカットにすぎません。さらに制御するには、execute
メソッドを使用してレスポンスを処理します。
例:
同期化
ノンブロッキング
ClientGraphQlResponse response = graphQlClient.document(document).executeSync();
if (!response.isValid()) {
// Request failure... (1)
}
ClientResponseField field = response.field("project");
if (field.getValue() == null) {
if (field.getErrors().isEmpty()) {
// Optional field set to null... (2)
}
else {
// Field failure... (3)
}
}
Project project = field.toEntity(Project.class); (4)
Mono<Project> projectMono = graphQlClient.document(document)
.execute()
.map((response) -> {
if (!response.isValid()) {
// Request failure... (1)
}
ClientResponseField field = response.field("project");
if (field.getValue() == null) {
if (field.getErrors().isEmpty()) {
// Optional field set to null... (2)
}
else {
// Field failure... (3)
}
}
return field.toEntity(Project.class); (4)
});
1 | レスポンスにはデータがなく、エラーのみがあります |
2 | DataFetcher によって null に設定されたフィールド |
3 | null で、関連するエラーがあるフィールド |
4 | 指定されたパスでデータをデコードします |
ドキュメントソース
リクエストのドキュメントは String
であり、ローカル変数または定数で定義されるか、コード生成されたリクエストオブジェクトによって生成されます。
クラスパス上の "graphql-documents/"
に、拡張子 .graphql
または .gql
のドキュメントファイルを作成し、ファイル名で参照することもできます。
例: src/main/resources/graphql-documents
内の projectReleases.graphql
というファイルが与えられた場合、内容は次のとおりです。
query projectReleases($slug: ID!) {
project(slug: $slug) {
name
releases {
version
}
}
}
その後、次のことができます。
Project project = graphQlClient.documentName("projectReleases") (1)
.variable("slug", "spring-framework") (2)
.retrieveSync("projectReleases.project")
.toEntity(Project.class);
1 | "projectReleases.graphql" からドキュメントをロードします |
2 | 変数値を提供します。 |
IntelliJ の "JS GraphQL" プラグインは、コード補完 で GraphQL クエリファイルをサポートします。
GraphQlClient
ビルダーを使用して、名前でドキュメントをロードするために DocumentSource
をカスタマイズできます。
サブスクリプションリクエスト
サブスクリプションリクエストには、データをストリーミングできるクライアントトランスポートが必要です。これをサポートする GraphQlClient
を作成する必要があります。
HttpGraphQlClient とサーバー送信イベント
WebSocketGraphQlClient と WebSocket
RSocketGraphQlClient RS ソケットを使用する場合
取得
サブスクリプションストリームを開始するには、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.getValue() == null) {
if (field.getErrors().isEmpty()) {
// Optional field set to null...
}
else {
// Field failure...
}
}
return field.toEntity(String.class);
});
インターセプト
GraphQlClient.SyncBuilder
で作成されたトランスポートをブロックするには、クライアント経由のすべてのリクエストをインターセプトする SyncGraphQlClientInterceptor
を作成します。
import org.springframework.graphql.client.ClientGraphQlRequest;
import org.springframework.graphql.client.ClientGraphQlResponse;
import org.springframework.graphql.client.SyncGraphQlClientInterceptor;
public class SyncInterceptor implements SyncGraphQlClientInterceptor {
@Override
public ClientGraphQlResponse intercept(ClientGraphQlRequest request, Chain chain) {
// ...
return chain.next(request);
}
}
GraphQlClient.Builder
で作成されたノンブロッキングトランスポートの場合は、クライアント経由のすべてのリクエストをインターセプトする GraphQlClientInterceptor
を作成します。
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.graphql.client.ClientGraphQlRequest;
import org.springframework.graphql.client.ClientGraphQlResponse;
import org.springframework.graphql.client.GraphQlClientInterceptor;
public 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 = URI.create("wss://localhost:8080/graphql");
WebSocketClient client = new ReactorNettyWebSocketClient();
WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient.builder(url, client)
.interceptor(new MyInterceptor())
.build();
DGS コードジェネ
ミューテーション、クエリ、サブスクリプションなどの操作をテキストとして提供する代わりに、DGS コードジェネ [GitHub] (英語) ライブラリを使用してクライアント API クラスを生成し、流れるような API を使用してリクエストを定義できます。
Spring for GraphQL は、GraphQlClient
をラップし、生成されたクライアント API クラスを使用してリクエストを準備するのに役立つ DgsGraphQlClient を提供します。
例: 次のスキーマがある場合:
type Query {
books: [Book]
}
type Book {
id: ID
name: String
}
リクエストは次のように実行できます。
HttpGraphQlClient client = ... ;
DgsGraphQlClient dgsClient = DgsGraphQlClient.create(client); (1)
List<Book> books = dgsClient.request(new BooksGraphQLQuery()) (2)
.projection(new BooksProjectionRoot<>().id().name()) (3)
.retrieveSync("books")
.toEntityList(Book.class);
1 | - 任意の GraphQlClient をラップして DgsGraphQlClient を作成します。 |
2 | ・リクエストに対する動作を指定します。 |
3 | - 選択セットを定義します。 |