Client

Spring for GraphQL 包括通过 HTTP、WebSocket 和 RSocket 执行 GraphQL 请求的客户端支持。

Spring for GraphQL includes client support to execute GraphQL requests over HTTP, WebSocket, and RSocket.

GraphQlClient

GraphQlClient 为与底层传输无关的 GraphQL 请求定义了一个通用工作流,因此无论使用何种传输,执行请求的方式都是相同的。

GraphQlClient defines a common workflow for GraphQL requests independent of the underlying transport, so the way you perform requests is the same no matter what transport is in use.

提供以下特定于传输的 GraphQlClient 扩展:

The following transport specific GraphQlClient extensions are available:

每个都定义一个 Builder,其中包含与传输相关选项。所有构建器都从一个通用的基本 GraphQlClient xref:client.adoc#client.graphqlclient.builder[Builder 扩展而来,该客户端具有适用于所有传输的选项。

Each defines a Builder with options relevant to the transport. All builders extend from a common, base GraphQlClient Builder with options applicable to all transports.

构建 `GraphQlClient`后,便可以开始制作 requests

Once GraphQlClient is built you can begin to make requests.

通常,请求的 GraphQL 操作以文本形式提供。或者,你也可以通过 DgsGraphQlClient,使用 DGS Codegen 客户端 API 类,它可以包装任何上述 `GraphQlClient`扩展。

Typically, the GraphQL operation for a request is provided as text. Alternatively, you can use DGS Codegen client API classes through DgsGraphQlClient, which can wrap any of the above GraphQlClient extensions.

HTTP Sync

HttpSyncGraphQlClient 使用 RestClient 通过拦截器的阻塞传输契约和链来通过 HTTP 执行 GraphQL 请求。

HttpSyncGraphQlClient uses RestClient to execute GraphQL requests over HTTP through a blocking transport contract and chain of interceptors.

RestClient restClient = ... ;
HttpSyncGraphQlClient graphQlClient = HttpSyncGraphQlClient.create(restClient);

创建 `HttpSyncGraphQlClient`后,便可以使用相同的 API 开始 execute requests,该 API 与基础传输无关。如果你需要更改任何传输特定详细信息,请在现有 `HttpSyncGraphQlClient`上使用 `mutate()`来创建一个带有定制设置的新实例:

Once HttpSyncGraphQlClient is created, you can begin to execute requests using the same API, independent of the underlying transport. If you need to change any transport specific details, use mutate() on an existing HttpSyncGraphQlClient to create a new instance with customized settings:

   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 请求。

HttpGraphQlClient uses WebClient to execute GraphQL requests over HTTP through a non-blocking transport contract and chain of interceptors.

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

创建 `HttpGraphQlClient`后,便可以使用相同的 API 开始 execute requests,该 API 与基础传输无关。如果你需要更改任何传输特定详细信息,请在现有 `HttpGraphQlClient`上使用 `mutate()`来创建一个带有定制设置的新实例:

Once HttpGraphQlClient is created, you can begin to execute requests using the same API, independent of the underlying transport. If you need to change any transport specific details, use mutate() on an existing HttpGraphQlClient to create a new instance with customized settings:

   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 构建的,您可以按照如下方式创建它:

WebSocketGraphQlClient executes GraphQL requests over a shared WebSocket connection. It is built using the WebSocketClient from Spring WebFlux and you can create it as follows:

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

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

HttpGraphQlClient 相反,WebSocketGraphQlClient 是面向连接的,这意味着它需要在发出任何请求之前建立一个连接。当开始发出请求时,连接会以透明的方式建立。或者,使用客户端的 start() 方法在任何请求之前显式地建立连接。

In contrast to HttpGraphQlClient, the WebSocketGraphQlClient is connection oriented, which means it needs to establish a connection before making any requests. As you begin to make requests, the connection is established transparently. Alternatively, use the client’s start() method to establish the connection explicitly before any requests.

除了面向连接之外,WebSocketGraphQlClient 也是复用的。它为所有请求维护一个单一的共享连接。如果连接丢失,它将在下一个请求中或在再次调用 start() 时重新建立。你还可以使用客户端的 stop() 方法,该方法会取消正在进行的请求、关闭连接并拒绝新的请求。

In addition to being connection-oriented, WebSocketGraphQlClient is also multiplexed. It maintains a single, shared connection for all requests. If the connection is lost, it is re-established on the next request or if start() is called again. You can also use the client’s stop() method which cancels in-progress requests, closes the connection, and rejects new requests.

对每个服务器使用一个`WebSocketGraphQlClient`实例,以便对该服务器的所有请求都使用单个共享连接。每个客户端实例建立自己的连接,这通常不是单个服务器的意图。

Use a single WebSocketGraphQlClient instance for each server in order to have a single, shared connection for all requests to that server. Each client instance establishes its own connection and that is typically not the intent for a single server.

创建 `WebSocketGraphQlClient`后,便可以使用相同的 API 开始 execute requests,该 API 与基础传输无关。如果你需要更改任何传输特定详细信息,请在现有 `WebSocketGraphQlClient`上使用 `mutate()`来创建一个带有定制设置的新实例:

Once WebSocketGraphQlClient is created, you can begin to execute requests using the same API, independent of the underlying transport. If you need to change any transport specific details, use mutate() on an existing WebSocketGraphQlClient to create a new instance with customized settings:

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...

Interceptor

除了执行请求之外, GraphQL over WebSocket协议还定义了一些面向连接的消息。例如,客户端发送 `"connection_init"`和服务器以 `"connection_ack"`响应,作为连接启动时的响应。

The GraphQL over WebSocket protocol defines a number of connection oriented messages in addition to executing requests. For example, a client sends "connection_init" and the server responds with "connection_ack" at the start of a connection.

为了进行 WebSocket 传输特定的拦截,你可以创建一个 WebSocketGraphQlClientInterceptor

For WebSocket transport specific interception, you can create a 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 的拦截器。

Register the above interceptor as any other GraphQlClientInterceptor and use it also to intercept GraphQL requests, but note there can be at most one interceptor of type WebSocketGraphQlClientInterceptor.

RSocket

RSocketGraphQlClient 使用 RSocketRequester 通过 RSocket 请求执行 GraphQL 请求。

RSocketGraphQlClient uses RSocketRequester to execute GraphQL requests over RSocket requests.

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

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

HttpGraphQlClient 相反,RSocketGraphQlClient 是面向连接的,这意味着它需要在发出任何请求之前建立一个会话。当你开始发出请求时,会话将以透明的方式建立。或者,使用客户端的 start() 方法在发出任何请求之前显式地建立会话。

In contrast to HttpGraphQlClient, the RSocketGraphQlClient is connection oriented, which means it needs to establish a session before making any requests. As you begin to make requests, the session is established transparently. Alternatively, use the client’s start() method to establish the session explicitly before any requests.

RSocketGraphQlClient 也是复用的。它为所有请求维护一个单一的、共享的会话。如果会话丢失,它将在下一个请求中重新建立,或者如果再次调用 start()。你还可以使用客户端的 stop() 方法,该方法会取消正在进行的请求,关闭会话,并拒绝新请求。

RSocketGraphQlClient is also multiplexed. It maintains a single, shared session for all requests. If the session is lost, it is re-established on the next request or if start() is called again. You can also use the client’s stop() method which cancels in-progress requests, closes the session, and rejects new requests.

对每个服务器使用一个`RSocketGraphQlClient`实例,以便对该服务器的所有请求都使用单个共享会话。每个客户端实例建立自己的连接,这通常不是单个服务器的意图。

Use a single RSocketGraphQlClient instance for each server in order to have a single, shared session for all requests to that server. Each client instance establishes its own connection and that is typically not the intent for a single server.

创建 `RSocketGraphQlClient`后,便可以使用相同的 API 开始 execute requests,该 API 与基础传输无关。

Once RSocketGraphQlClient is created, you can begin to execute requests using the same API, independent of the underlying transport.

Builder

GraphQlClient 定义了一个父级 BaseBuilder,其中包含所有扩展的构建器的公共配置选项。目前,它允许你配置:

GraphQlClient defines a parent BaseBuilder with common configuration options for the builders of all extensions. Currently, it has lets you configure:

  • DocumentSource strategy to load the document for a request from a file

  • Interception of executed requests

BaseBuilder 进一步由以下内容扩展:

BaseBuilder is further extended by the following:

  • SyncBuilder - blocking execution stack with a chain of `SyncGraphQlInterceptor’s.

  • Builder - non-blocking execution stack with chain of `GraphQlInterceptor’s.

Requests

获得 GraphQlClient后,便可以通过 retrieveexecute方法开始执行请求。

Once you have a GraphQlClient, you can begin to perform requests via retrieve or execute methods.

Retrieve

以下内容检索和解码查询数据:

The below retrieves and decodes the data for a query:

  • Sync

  • Non-Blocking

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 The operation to perform.
2 The path under the "data" key in the response map to decode from.
3 Decode the data at the path to the target type.

输入文档是一个 String,它可能是文本或通过代码生成的请求对象生成的。你还可以定义文件中的文档,并使用 Document Source按文件名重新确定它们。

The input document is a String that could be a literal or produced through a code generated request object. You can also define documents in files and use a Document Source to resole them by file name.

该路径是相对于“data”键的,它使用简单的点号(“.”)分隔符号表示嵌套字段,列表元素可以使用可选的数组索引,例如 "project.name""project.releases[0].version"

The path is relative to the "data" key and uses a simple dot (".") separated notation for nested fields with optional array indices for list elements, e.g. "project.name" or "project.releases[0].version".

如果给定的路径不存在,或者字段值是 null 并存在错误,解码将导致 FieldAccessExceptionFieldAccessException 提供对响应和字段的访问权限:

Decoding can result in FieldAccessException if the given path is not present, or the field value is null and has an error. FieldAccessException provides access to the response and the field:

  • Sync

  • Non-Blocking

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

Retrieve只是一个快捷方式,用于从响应映射中的单个路径进行解码。要获得更多控制,请使用 `execute`方法并处理响应:

Retrieve is only a shortcut to decode from a single path in the response map. For more control, use the execute method and handle the response:

例如:

For example:

  • Sync

  • Non-Blocking

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 The response does not have data, only errors
2 Field that is null and has an associated error
3 Field that was set to null by its DataFetcher
4 Decode the data at the given path

Document Source

请求的文档是一个 String,它可以在局部变量或常量中定义,或者可以通过代码生成请求对象生成。

The document for a request is a String that may be defined in a local variable or constant, or it may be produced through a code generated request object.

你还可以创建扩展名为 .graphql.gql 的文档文件,这些文件位于类路径上的 "graphql-documents/" 下,并通过文件名引用它们。

You can also create document files with extensions .graphql or .gql under "graphql-documents/" on the classpath and refer to them by file name.

例如,给定名为 projectReleases.graphql、内容为:的文件在 src/main/resources/graphql-documents 中:

For example, given a file called projectReleases.graphql in src/main/resources/graphql-documents, with content:

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

然后你可以:

You can then:

Project project = graphQlClient.documentName("projectReleases") 1
		.variable("slug", "spring-framework") 2
		.retrieveSync()
		.toEntity(Project.class);
1 Load the document from "projectReleases.graphql"
2 Provide variable values.

IntelliJ 的 “JS GraphQL” 插件支持带有代码补全功能的 GraphQL 查询文件。

The "JS GraphQL" plugin for IntelliJ supports GraphQL query files with code completion.

你可以使用 Builder定制 DocumentSource,以按名称加载文档。

You can use the GraphQlClient Builder to customize the DocumentSource for loading documents by names.

Subscription Requests

订阅请求需要一种能够流式传输数据的客户端传输。你需要创建支持此功能的`GraphQlClient`:

Subscription requests require a client transport that is capable of streaming data. You will need to create a GraphQlClient that support this:

Retrieve

要启动订阅流,请使用与 retrieve类似的 retrieveSubscription,但对于单个响应返回一个响应流,每个响应都解码为一些数据:

To start a subscription stream, use retrieveSubscription which is similar to retrieve for a single response but returning a stream of responses, each decoded to some data:

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

如果订阅由于服务器端出现“错误”消息而结束,则`Flux`可能会以`SubscriptionErrorException`终止。该异常提供对从“错误”消息中解码的GraphQL错误的访问。

The Flux may terminate with SubscriptionErrorException if the subscription ends from the server side with an "error" message. The exception provides access to GraphQL errors decoded from the "error" message.

如果底层连接关闭或丢失,则`Flux`可能会以`GraphQlTransportException`(如`WebSocketDisconnectedException`)终止。在这种情况下,你可以使用`retry`运算符重新启动订阅。

The Flux may termiate with GraphQlTransportException such as WebSocketDisconnectedException if the underlying connection is closed or lost. In that case you can use the retry operator to restart the subscription.

要从客户端结束订阅,必须取消`Flux`,反过来WebSocket传输会向服务器发送“完成”消息。如何取消`Flux`取决于它的使用方式。一些运算符(例如`take`或`timeout`)本身会取消`Flux`。如果你使用`Subscriber`订阅`Flux`,你可以获取对`Subscription`的引用并通过它取消。`onSubscribe`运算符还提供对`Subscription`的访问。

To end the subscription from the client side, the Flux must be cancelled, and in turn the WebSocket transport sends a "complete" message to the server. How to cancel the Flux depends on how it is used. Some operators such as take or timeout themselves cancel the Flux. If you subscribe to the Flux with a Subscriber, you can get a reference to the Subscription and cancel through it. The onSubscribe operator also provides access to the Subscription.

Execute

Retrieve只是一个快捷方式,用于从每个响应映射中的单个路径进行解码。要获得更多控制,请使用 `executeSubscription`方法并直接处理每个响应:

Retrieve is only a shortcut to decode from a single path in each response map. For more control, use the executeSubscription method and handle each response directly:

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)
		});

Interception

对于使用`GraphQlClient.SyncBuilder`创建的阻塞式传输,你可以创建一个`SyncGraphQlClientInterceptor`来拦截通过客户端的所有请求:

For blocking transports created with the GraphQlClient.SyncBuilder, you create a SyncGraphQlClientInterceptor to intercept all requests through the client:

static class MyInterceptor implements SyncGraphQlClientInterceptor {

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

对于使用`GraphQlClient.Builder`创建的非阻塞式传输,你可以创建一个`GraphQlClientInterceptor`来拦截通过客户端的所有请求:

For non-blocking transports created with GraphQlClient.Builder, you create a GraphQlClientInterceptor to intercept all requests through the client:

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);
	}

}

一旦创建了拦截器,就通过客户端构建器注册它。例如:

Once the interceptor is created, register it through the client builder. For example:

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

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

DGS Codegen

除了将操作(如 mutation、query 或 subscription)作为文本提供之外,你还可以使用 DGS Codegen库来生成客户端 API 类,让你可以使用流畅的 API 来定义请求。

As an alternative to providing the operation such as a mutation, query, or subscription as text, you can use the DGS Codegen library to generate client API classes that let you use a fluent API to define the request.

Spring for GraphQL 提供了 DgsGraphQlClient,它封装了任何 GraphQlClient,并帮助使用生成的 clientAPI 类准备请求。

Spring for GraphQL provides DgsGraphQlClient that wraps any GraphQlClient and helps to prepare the request with generated client API classes.

例如,给定以下模式:

For example, given the following schema:

type Query {
    books: [Book]
}

type Book {
    id: ID
    name: String
}

你可以这样执行请求:

You can perform a request as follows:

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 - Create DgsGraphQlClient by wrapping any GraphQlClient.
2 - Specify the operation for the request.
3 - Define the selection set.