Client

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

GraphQlClient

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

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

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

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

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

HTTP Sync

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

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

创建 `HttpSyncGraphQlClient`后,便可以使用相同的 API 开始 execute requests,该 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 开始 execute requests,该 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 开始 execute requests,该 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...

Interceptor

除了执行请求之外, GraphQL over WebSocket协议还定义了一些面向连接的消息。例如,客户端发送 `"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 的拦截器。

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 开始 execute requests,该 API 与基础传输无关。

Builder

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

  • DocumentSource 策略可从文件为请求加载文档

  • Interception of executed requests

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

  • SyncBuilder - 使用 `SyncGraphQlInterceptor’s 的链来阻止执行堆栈。

  • Builder - 使用 `GraphQlInterceptor’s 的链来非阻塞执行堆栈。

Requests

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

Retrieve

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

  • 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 从其中解码响应映射的“data”键下的路径。
3 将路径中的数据解码为目标类型。

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

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

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

  • 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`方法并处理响应:

例如:

  • 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 响应中没有数据,只有错误
2 字段为 null 并且有相关错误
3 字段通过其 DataFetcher 设置为 null
4 解码给定路径中的数据

Document Source

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

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

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

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 Provide variable values.

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

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

Subscription Requests

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

Retrieve

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

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

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

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

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

Execute

Retrieve只是一个快捷方式,用于从每个响应映射中的单个路径进行解码。要获得更多控制,请使用 `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)
		});

Interception

对于使用`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 Codegen

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

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

例如,给定以下模式:

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 - 定义选择集。