クライアント
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 = ... ;
HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.create(restClient);
HttpSyncGraphQlClient
が作成されると、基礎となるトランスポートとは関係なく、同じ API を使用してリクエストの実行を開始できます。トランスポート固有の詳細を変更する必要がある場合は、既存の HttpSyncGraphQlClient
で mutate()
を使用して、カスタマイズされた設定で新しいインスタンスを作成します。
RestClient restClient = ... ;
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 = ... ;
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 WebFlux の WebSocketClient を使用して構築されており、次のように作成できます。
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...
WebSocketGraphQlClient
は、他のメッセージが送受信されていないときに接続をアクティブに保つために、定期的な ping メッセージの送信をサポートしています。これを有効にするには、次の手順を実行します。
URI url = ... ;
WebSocketClient client = ... ;
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(url);
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> 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
は、レスポンスとフィールドへのアクセスを提供します。
同期化
ノンブロッキング
try {
Project project = graphQlClient.document(document)
.retrieveSync("project")
.toEntity(Project.class);
}
catch (FieldAccessException ex) {
ClientGraphQlResponse response = ex.getResponse();
// ...
ClientResponseField field = ex.getField();
// ...
}
Mono<Project> projectMono = graphQlClient.document(document)
.retrieve("project")
.toEntity(Project.class)
.onErrorResume(FieldAccessException.class, ex -> {
ClientGraphQlResponse response = ex.getResponse();
// ...
ClientResponseField field = ex.getField();
// ...
});
実行
取得は、レスポンスマップ内の単一のパスからデコードするためのショートカットにすぎません。さらに制御するには、execute
メソッドを使用してレスポンスを処理します。
例:
同期化
ノンブロッキング
ClientGraphQlResponse response = graphQlClient.document(document).executeSync();
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)
}
}
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.hasValue()) {
if (field.getError() != null) {
// Field failure... (2)
}
else {
// Optional field set to null... (3)
}
}
return field.toEntity(Project.class); (4)
});
1 | レスポンスにはデータがなく、エラーのみがあります |
2 | null で、関連するエラーがあるフィールド |
3 | DataFetcher によって 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()
.toEntity(Project.class);
1 | "projectReleases.graphql" からドキュメントを読み込みます |
2 | 変数値を提供します。 |
このアプローチは、クエリのフラグメントをロードする場合にも機能します。フラグメントは、リクエストドキュメント内の繰り返しを回避する再利用可能なフィールド選択セットです。例: 複数のクエリで …releases
フラグメントを使用できます。
query frameworkReleases {
project(slug: "spring-framework") {
name
...releases
}
}
query graphqlReleases {
project(slug: "spring-graphql") {
name
...releases
}
}
このフラグメントは、再利用のために別のファイルで定義できます。
fragment releases on Project {
releases {
version
}
}
次に、このフラグメントをクエリドキュメントとともに送信できます。
Project project = graphQlClient.documentName("projectReleases") (1)
.fragmentName("releases") (2)
.retrieveSync()
.toEntity(Project.class);
1 | "projectReleases.graphql" からドキュメントを読み込みます |
2 | "releases.graphql" からフラグメントをロードし、ドキュメントに追加します |
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.hasValue()) {
if (field.getError() != null) {
// Field failure...
}
else {
// Optional field set to null... (3)
}
}
return field.toEntity(String.class)
});
インターセプト
GraphQlClient.SyncBuilder
で作成されたトランスポートをブロックするには、クライアント経由のすべてのリクエストをインターセプトする SyncGraphQlClientInterceptor
を作成します。
static class MyInterceptor implements SyncGraphQlClientInterceptor {
@Override
public ClientGraphQlResponse intercept(ClientGraphQlRequest request, Chain chain) {
// ...
return chain.next(request);
}
}
GraphQlClient.Builder
で作成されたノンブロッキングトランスポートの場合は、クライアント経由のすべてのリクエストをインターセプトする 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();
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()
.toEntityList(Book.class);
1 | - 任意の GraphQlClient をラップして DgsGraphQlClient を作成します。 |
2 | ・リクエストに対する動作を指定します。 |
3 | - 選択セットを定義します。 |