1.概述
Spring for GraphQL 为基于GraphQL Java构建的 Spring 应用程序提供支持 。这是两个团队之间的联合协作。我们的共同理念是减少固执己见,更专注于全面和广泛的支持。
Spring for GraphQL 是 GraphQL Java 团队的 GraphQL Java Spring项目的继承者。它旨在成为所有 Spring、GraphQL 应用程序的基础。
该项目目前正处于迈向 1.0 版本的里程碑阶段,并正在寻找反馈。请使用我们的 问题跟踪器报告问题、讨论设计问题或请求功能。
2. 要求
Spring for GraphQL 需要以下内容作为基线:
-
JDK8
-
Spring框架 5.3
-
GraphQL Java 18
-
QueryDSL 或 Query by Example 的 Spring Data 2021.1.0 或更高版本
3. 服务器传输
Spring for GraphQL 支持服务器通过 HTTP、WebSocket 和 RSocket 处理 GraphQL 请求。
3.1。HTTP
GraphQlHttpHandler
通过 HTTP 请求处理 GraphQL,并委托给
拦截链以执行请求。有两种变体,一种用于 Spring MVC,一种用于 Spring WebFlux。两者都异步处理请求并具有等效的功能,但分别依赖阻塞和非阻塞 I/O 来编写 HTTP 响应。
请求必须使用 HTTP POST 和 GraphQL 请求详细信息作为 JSON 包含在请求正文中,如提议的
GraphQL over HTTP
规范中所定义。成功解码 JSON 正文后,HTTP 响应状态始终为 200(OK),GraphQL 请求执行的任何错误都会出现在 GraphQL 响应的“错误”部分。媒体类型的默认和首选选择是
"application/graphql+json"
,但"application/json"
也受支持,如规范中所述。
GraphQlHttpHandler
RouterFunction
可以通过声明一个bean 并使用RouterFunctions
Spring MVC 或 WebFlux 创建路由来公开为 HTTP 端点。Boot starter 会执行此操作,有关详细信息,请参阅
Web Endpoints部分,或检查GraphQlWebMvcAutoConfiguration
或GraphQlWebFluxAutoConfiguration
包含实际配置。
Spring for GraphQL 存储库包含一个 Spring MVC HTTP 示例应用程序。
3.2. 网络套接字
GraphQlWebSocketHandler
基于
graphql-ws库中定义
的协议处理 GraphQL over WebSocket 请求。在 WebSocket 上使用 GraphQL 的主要原因是订阅允许发送 GraphQL 响应流,但它也可以用于具有单个响应的常规查询。处理程序将每个请求委托给拦截链以进一步执行请求。
基于 WebSocket 协议的 GraphQL
有两种这样的协议,一种在 subscriptions-transport-ws 库中,另一种在 graphql-ws库中。前者不主动,由后者接替。阅读这篇 博文了解历史。 |
有两种变体GraphQlWebSocketHandler
,一种用于 Spring MVC,一种用于 Spring WebFlux。两者都异步处理请求并具有等效的功能。WebFlux 处理程序还使用非阻塞 I/O 和背压来流式传输消息,这很有效,因为在 GraphQL Java 中,订阅响应是 Reactive Streams
Publisher
。
该graphql-ws
项目列出了许多
供客户使用的配方。
GraphQlWebSocketHandler
SimpleUrlHandlerMapping
通过声明一个bean 并使用它将处理程序映射到 URL 路径,可以将其公开为 WebSocket 端点
。Boot starter 具有启用此功能的选项,有关详细信息,请参阅
Web Endpoints部分,或者检查GraphQlWebMvcAutoConfiguration
或GraphQlWebFluxAutoConfiguration
它包含的实际配置。
Spring for GraphQL 存储库包含一个 WebFlux WebSocket 示例应用程序。
3.3. RSocket
GraphQlRSocketHandler
通过 RSocket 请求处理 GraphQL。查询和突变是预期并作为 RSocketrequest-response
交互处理,而订阅则作为request-stream
.
GraphQlRSocketHandler
可以使用@Controller
映射到 GraphQL 请求路由的委托。例如:
@Controller
public class GraphQlRSocketController {
private final GraphQlRSocketHandler handler;
GraphQlRSocketController(GraphQlRSocketHandler handler) {
this.handler = handler;
}
@MessageMapping("graphql")
public Mono<Map<String, Object>> handle(Map<String, Object> payload) {
return this.handler.handle(payload);
}
@MessageMapping("graphql")
public Flux<Map<String, Object>> handleSubscription(Map<String, Object> payload) {
return this.handler.handleSubscription(payload);
}
}
3.4. 拦截
用于HTTP和
WebSocket的 Spring MVC 和 Spring WebFlux 传输处理程序都委托给同一个WebGraphQlInterceptor
链,然后ExecutionGraphQlService
调用 GraphQL Java 引擎。您可以使用它来拦截任何 Web 传输上的 GraphQL 请求。
AWebGraphQlInterceptor
公开底层传输(HTTP 或 WebSocket 握手)请求的详细信息,并允许自定义graphql.ExecutionInput
为 GraphQL Java 准备的请求。例如,要提取 HTTP 标头并通过以下方式将其提供给数据获取器GraphQLContext
:
class HeaderInterceptor implements WebGraphQlInterceptor {
@Override
public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) {
List<String> headerValue = request.getHeaders().get("myHeader");
request.configureExecutionInput((executionInput, builder) ->
builder.graphQLContext(Collections.singletonMap("myHeader", headerValue)).build());
return chain.next(request);
}
}
然后ADataFetcher
可以访问这个值,例如从一个带
注释的控制器方法:
@Controller
class MyController {
@QueryMapping
Person person(@ContextValue String myHeader) {
// ...
}
}
拦截器还可以自定义 HTTP 响应标头,或检查和/或
graphql.ExecutionResult
从 GraphQL Java 转换:
class MyInterceptor implements WebGraphQlInterceptor {
@Override
public Mono<WebGraphQlResponse> intercept(WebGraphQlRequest request, Chain chain) {
return chain.next(request)
.map(response -> {
Object data = response.getData();
Object updatedData = ... ;
return response.transform(builder -> builder.data(updatedData));
});
}
}
WebGraphQlHandler
有一个构建器来创建WebGraphInterceptor
链。Boot starter 使用它,请参阅 Boot 的
Web Endpoints部分。
RSocket传输处理程序委托给一个类似的链GraphQlInterceptor
,您可以使用它来拦截 GraphQL over RSocket 请求。
4. 请求执行
ExecutionGraphQlService
是调用 GraphQL Java 执行请求的主要 Spring 抽象。底层传输,例如服务器传输,委托
ExecutionGraphQlService
处理请求。
主要实现DefaultExecutionGraphQlService
配置了一个
GraphQlSource
用于访问graphql.GraphQL
要调用的实例的方法。
4.1。GraphQLSource
GraphQlSource
是用于访问
graphql.GraphQL
实例以用于请求执行的核心 Spring 抽象。它提供了一个构建器 API 来初始化 GraphQL Java 并构建一个GraphQlSource
.
默认GraphQlSource
构建器可通过 访问
GraphQlSource.schemaResourceBuilder()
,支持
ReactiveDataFetcher
、Context Propagation和Exception Resolution。
Spring Boot启动器通过默认值初始化一个
GraphQlSource
实例,GraphQlSource.Builder
并且还启用了以下功能:
-
从可配置的位置加载模式文件。
-
公开 适用于 的属性
GraphQlSource.Builder
。 -
检测GraphQL 指标的Instrumentation bean 。
-
检测
DataFetcherExceptionResolver
bean 以解决异常。
对于进一步的自定义,您可以声明自己的GraphQlSourceBuilderCustomizer
bean;例如,用于配置您自己的ExecutionIdProvider
:
@Configuration(proxyBeanMethods = false)
class GraphQlConfig {
@Bean
public GraphQlSourceBuilderCustomizer sourceBuilderCustomizer() {
return (builder) ->
builder.configureGraphQl(graphQlBuilder ->
graphQlBuilder.executionIdProvider(new CustomExecutionIdProvider()));
}
}
4.1.1。架构资源
GraphQlSource.Builder
可以配置一个或多个Resource
实例来解析和合并在一起。这意味着可以从几乎任何位置加载模式文件。
默认情况下,Spring Boot 启动器
从众所周知的类路径位置查找模式文件FileSystemResource
,但您可以通过 将其更改为文件系统上的位置,通过字节内容ByteArrayResource
,或实现
Resource
从远程位置加载模式文件的自定义或贮存。
4.1.2. 模式创建
默认情况下,GraphQlSource.Builder
使用 GraphQL JavaGraphQLSchemaGenerator
创建graphql.schema.GraphQLSchema
. 这适用于大多数应用程序,但如有必要,您可以通过构建器挂钩模式创建:
GraphQlSource.Builder builder = ...
builder.schemaResources(..)
.configureRuntimeWiring(..)
.schemaFactory((typeDefinitionRegistry, runtimeWiring) -> {
// create GraphQLSchema
})
这样做的主要原因是通过联合库创建模式。
GraphQlSource部分解释了如何使用 Spring Boot 进行配置。
4.1.3。RuntimeWiringConfigurer
您可以使用RuntimeWiringConfigurer
以下方式注册:
-
自定义标量类型。
-
指令处理代码。
-
TypeResolver
, 如果您需要覆盖类型的 默认值TypeResolver
。 -
DataFetcher
对于一个字段,尽管大多数应用程序将简单地配置AnnotatedControllerConfigurer
,它检测带注释的DataFetcher
处理程序方法。Spring Boot 启动器AnnotatedControllerConfigurer
默认添加。
与 Web 框架不同,GraphQL 不使用 Jackson 注释来驱动 JSON 序列化/反序列化。自定义数据类型及其序列化必须描述为 Scalars。 |
Spring Boot 启动器检测 bean 类型RuntimeWiringConfigurer
并将它们注册到GraphQlSource.Builder
. 这意味着在大多数情况下,您的配置中将包含以下内容:
@Configuration
public class GraphQlConfig {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer(BookRepository repository) {
GraphQLScalarType scalarType = ... ;
SchemaDirectiveWiring directiveWiring = ... ;
DataFetcher dataFetcher = QuerydslDataFetcher.builder(repository).single();
return wiringBuilder -> wiringBuilder
.scalar(scalarType)
.directiveWiring(directiveWiring)
.type("Query", builder -> builder.dataFetcher("book", dataFetcher));
}
}
如果您需要添加一个WiringFactory
,例如进行考虑模式定义的注册,请实现configure
同时接受该
RuntimeWiring.Builder
和一个输出的替代方法List<WiringFactory>
。这允许您添加任意数量的工厂,然后按顺序调用这些工厂。
4.1.4。默认TypeResolver
GraphQlSource.Builder
注册ClassNameTypeResolver
为默认TypeResolver
用于 GraphQL 接口和联合,这些接口和联合尚未通过RuntimeWiringConfigurer
. GraphQL Java中的 a 的目的TypeResolver
是确定从DataFetcher
GraphQL 接口或联合字段返回的值的 GraphQL 对象类型。
ClassNameTypeResolver
尝试将值的简单类名与 GraphQL 对象类型匹配,如果不成功,它还会导航其超类型,包括基类和接口,寻找匹配项。ClassNameTypeResolver
提供了一个选项来配置名称提取功能以及Class
GraphQL 对象类型名称映射,这应该有助于涵盖更多极端情况:
GraphQlSource.Builder builder = ...
ClassNameTypeResolver classNameTypeResolver = new ClassNameTypeResolver();
classNameTypeResolver.setClassNameExtractor((klass) -> {
// Implement Custom ClassName Extractor here
});
builder.defaultTypeResolver(classNameTypeResolver);
GraphQlSource部分解释了如何使用 Spring Boot 进行配置。
4.1.5。操作缓存
GraphQL Java 必须在执行操作之前对其进行解析和验证。这可能会显着影响性能。为了避免重新解析和验证的需要,应用程序可以配置PreparsedDocumentProvider
缓存和重用 Document 实例。GraphQL
Java 文档通过PreparsedDocumentProvider
.
在 Spring GraphQL 中,您可以PreparsedDocumentProvider
通过
GraphQlSource.Builder#configureGraphQl
: 注册一个。
// Typically, accessed through Spring Boot's GraphQlSourceBuilderCustomizer
GraphQlSource.Builder builder = ...
// Create provider
PreparsedDocumentProvider provider = ...
builder.schemaResources(..)
.configureRuntimeWiring(..)
.configureGraphQl(graphQLBuilder -> graphQLBuilder.preparsedDocumentProvider(provider))
GraphQlSource部分解释了如何使用 Spring Boot 进行配置。
4.1.6。指令
GraphQL 语言支持“在 GraphQL 文档中描述备用运行时执行和类型验证行为”的指令。指令类似于 Java 中的注释,但在 GraphQL 文档中的类型、字段、片段和操作上声明。
GraphQL Java 提供SchemaDirectiveWiring
合约来帮助应用程序检测和处理指令。有关更多详细信息,请参阅
GraphQL Java 文档中的模式指令。
在 Spring GraphQL 中,您可以SchemaDirectiveWiring
通过
RuntimeWiringConfigurer
. Spring Boot 启动器会检测到此类 bean,因此您可能会遇到以下情况:
@Configuration
public class GraphQlConfig {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer() {
return builder -> builder.directiveWiring(new MySchemaDirectiveWiring());
}
}
有关指令支持的示例,请查看 Graphql Java 库的扩展验证。 |
4.2. 反应性DataFetcher
默认GraphQlSource
构建器支持 aDataFetcher
返回Mono
或Flux
将其调整为将CompletableFuture
值Flux
聚合并转换为 List 的地方,除非请求是 GraphQL 订阅请求,在这种情况下,返回值仍然是Publisher
用于流式传输 GraphQL 响应的 Reactive Streams。
反应式DataFetcher
可以依赖从传输层传播的 Reactor 上下文的访问,例如从 WebFlux 请求处理,请参阅
WebFlux 上下文。
4.3. 上下文传播
Spring for GraphQL 支持从
Server Transports透明地传播上下文,通过 GraphQL Java,以及DataFetcher
它调用的其他组件。这包括ThreadLocal
来自 Spring MVC 请求处理线程的上下文和Context
来自 WebFlux 处理管道的 Reactor。
4.3.1。WebMvc
GraphQL Java 调用的ADataFetcher
和其他组件可能并不总是在与 Spring MVC 处理程序相同的线程上执行,例如,如果异步
WebGraphQlInterceptor
或DataFetcher
切换到不同的线程。
Spring for GraphQL 支持将ThreadLocal
值从 Servlet 容器线程传播到线程 aDataFetcher
以及由 GraphQL Java 调用以在其上执行的其他组件。为此,应用程序需要创建一个ThreadLocalAccessor
来提取
ThreadLocal
感兴趣的值:
public class RequestAttributesAccessor implements ThreadLocalAccessor {
private static final String KEY = RequestAttributesAccessor.class.getName();
@Override
public void extractValues(Map<String, Object> container) {
container.put(KEY, RequestContextHolder.getRequestAttributes());
}
@Override
public void restoreValues(Map<String, Object> values) {
if (values.containsKey(KEY)) {
RequestContextHolder.setRequestAttributes((RequestAttributes) values.get(KEY));
}
}
@Override
public void resetValues(Map<String, Object> values) {
RequestContextHolder.resetRequestAttributes();
}
}
AThreadLocalAccessor
可以在WebGraphHandler
构建器中注册。Boot starter 检测到这种类型的 bean 并自动为 Spring MVC 应用程序注册它们,请参阅
Web Endpoints部分。
4.3.2. WebFlux
ReactiveDataFetcher
可以依赖对源自 WebFlux 请求处理链的 Reactor 上下文的访问。这包括由WebGraphQlInterceptor组件添加的 Reactor 上下文。
4.4. 异常解决
GraphQL Java 应用程序可以注册 aDataFetcherExceptionHandler
来决定如何在 GraphQL 响应的“错误”部分中表示来自数据层的异常。
Spring for GraphQL 有一个内置的DataFetcherExceptionHandler
配置供默认GraphQLSource
构建器使用。它允许应用程序注册一个或多个DataFetcherExceptionResolver
按顺序调用的 Spring 组件,直到将其解析Exception
为(可能为空的)graphql.GraphQLError
对象列表。
DataFetcherExceptionResolver
是一个异步合约。DataFetcherExceptionResolverAdapter
对于大多数实现,扩展和覆盖同步解决异常的方法resolveToSingleError
之一就足够了。resolveToMultipleErrors
AGraphQLError
可以通过 分配给一个类别graphql.ErrorClassification
。在 Spring GraphQL 中,您还可以分配 via ErrorType
which 具有以下常见分类,应用程序可以使用这些分类对错误进行分类:
-
BAD_REQUEST
-
UNAUTHORIZED
-
FORBIDDEN
-
NOT_FOUND
-
INTERNAL_ERROR
如果异常仍未解决,则默认情况下将其归类为INTERNAL_ERROR
带有通用消息的类,其中包括类别名称和executionId
from
DataFetchingEnvironment
。该消息是故意不透明的,以避免泄漏实现细节。应用程序可以使用 aDataFetcherExceptionResolver
来自定义错误详细信息。
未解决的异常记录在 ERROR 级别以及与executionId
发送到客户端的错误相关联。已解决的异常记录在 DEBUG 级别。
4.5. 批量加载
给定 aBook
和 its Author
,我们可以DataFetcher
为一本书创建一个,为它的作者创建另一个。这允许选择有或没有作者的书籍,但这意味着书籍和作者不会一起加载,这在查询多本书时尤其低效,因为每本书的作者都是单独加载的。这被称为 N+1 选择问题。
4.5.1。DataLoader
GraphQL Java 提供了一种DataLoader
批量加载相关实体的机制。您可以在
GraphQL Java 文档中找到完整的详细信息。以下是其工作原理的摘要:
-
DataLoader
在DataLoaderRegistry
给定唯一键的情况下,可以加载实体的寄存器。 -
DataFetcher
's 可以访问DataLoader
's 并使用它们通过 id 加载实体。 -
A
DataLoader
通过返回一个 future 来推迟加载,因此它可以分批完成。 -
DataLoader
'维护加载实体的每个请求缓存,可以进一步提高效率。
4.5.2.BatchLoaderRegistry
GraphQL Java 中完整的批处理加载机制需要实现几个BatchLoader
接口之一,然后将它们包装并注册为DataLoader
s 并在DataLoaderRegistry
.
Spring GraphQL 中的 API 略有不同。对于注册,只有一个中央BatchLoaderRegistry
公开工厂方法和一个构建器来创建和注册任意数量的批量加载函数:
@Configuration
public class MyConfig {
public MyConfig(BatchLoaderRegistry registry) {
registry.forTypePair(Long.class, Author.class).registerMappedBatchLoader((authorIds, env) -> {
// return Mono<Map<Long, Author>
});
// more registrations ...
}
}
Spring Boot starter 声明了一个BatchLoaderRegistry
bean,您可以将其注入到您的配置中,如上所示,或者注入到任何组件(如控制器)中,以便注册批量加载功能。反过来,它BatchLoaderRegistry
被注入到
DefaultExecutionGraphQlService
它确保DataLoader
每个请求注册的地方。
默认情况下,DataLoader
名称基于目标实体的类名。这允许@SchemaMapping
方法声明
具有泛型类型的DataLoader 参数,而无需指定名称。但是,如有必要,可以通过
BatchLoaderRegistry
构建器自定义名称以及其他DataLoader
选项。
很多情况下,在加载相关实体的时候,可以使用
@BatchMapping控制器方法,这是一个快捷方式,替代了需要直接使用BatchLoaderRegistry
和DataLoader
。s
BatchLoaderRegistry
也提供了其他重要的好处。GraphQLContext
它支持从批量加载函数和方法访问相同的内容@BatchMapping
,并确保对它们进行上下文传播。这就是为什么应用程序应该使用它的原因。可以直接执行您自己的DataLoader
注册,但这样的注册将放弃上述好处。
4.5.3. 测试批量加载
首先BatchLoaderRegistry
在 a 上执行注册DataLoaderRegistry
:
BatchLoaderRegistry batchLoaderRegistry = new DefaultBatchLoaderRegistry();
// perform registrations...
DataLoaderRegistry dataLoaderRegistry = DataLoaderRegistry.newRegistry().build();
batchLoaderRegistry.registerDataLoaders(dataLoaderRegistry, graphQLContext);
DataLoader
现在您可以按如下方式访问和测试个人:
DataLoader<Long, Book> loader = dataLoaderRegistry.getDataLoader(Book.class.getName());
loader.load(1L);
loader.loadMany(Arrays.asList(2L, 3L));
List<Book> books = loader.dispatchAndJoin(); // actual loading
assertThat(books).hasSize(3);
assertThat(books.get(0).getName()).isEqualTo("...");
// ...
5. 数据整合
Spring for GraphQL 允许您利用现有的 Spring 技术,遵循常见的编程模型,通过 GraphQL 公开底层数据源。
本节讨论 Spring Data 的集成层,它提供了一种将 Querydsl 或 Query by Example 存储库调整DataFetcher
为@GraphQlRepository
.
5.1。查询dsl
Spring for GraphQL 支持使用Querydsl通过 Spring Data Querydsl 扩展来获取数据 。Querydsl 提供了一种灵活但类型安全的方法,通过使用注释处理器生成元模型来表达查询谓词。
例如,将存储库声明为QuerydslPredicateExecutor
:
public interface AccountRepository extends Repository<Account, Long>,
QuerydslPredicateExecutor<Account> {
}
然后用它来创建一个DataFetcher
:
// For single result queries
DataFetcher<Account> dataFetcher =
QuerydslDataFetcher.builder(repository).single();
// For multi-result queries
DataFetcher<Iterable<Account>> dataFetcher =
QuerydslDataFetcher.builder(repository).many();
您现在可以DataFetcher
通过
RuntimeWiringConfigurer
.
从GraphQL请求参数DataFetcher
构建一个 Querydsl Predicate
,并使用它来获取数据。Spring Data 支持QuerydslPredicateExecutor
JPA、MongoDB 和 LDAP。
如果存储库是ReactiveQuerydslPredicateExecutor
,则构建器返回
DataFetcher<Mono<Account>>
或DataFetcher<Flux<Account>>
。Spring Data 支持 MongoDB 的这种变体。
5.1.1。构建设置
要在您的构建中配置 Querydsl,请遵循 官方参考文档:
例如:
dependencies {
//...
annotationProcessor "com.querydsl:querydsl-apt:$querydslVersion:jpa",
'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final',
'javax.annotation:javax.annotation-api:1.3.2'
}
compileJava {
options.annotationProcessorPath = configurations.annotationProcessor
}
webmvc -http示例将 Querydsl 用于
artifactRepositories
.
5.1.2. 定制
QuerydslDataFetcher
支持自定义 GraphQL 参数如何绑定到属性以创建 Querydsl Predicate
。默认情况下,每个可用属性的参数都绑定为“等于”。要自定义它,您可以使用QuerydslDataFetcher
构建器方法来提供QuerydslBinderCustomizer
.
存储库本身可能是QuerydslBinderCustomizer
. 这是在Auto-Registration期间自动检测并透明应用的。但是,在手动构建时,QuerydslDataFetcher
您将需要使用构建器方法来应用它。
QuerydslDataFetcher
支持接口和 DTO 投影来转换查询结果,然后返回这些结果以进行进一步的 GraphQL 处理。
要了解预测是什么,请参阅 Spring Data 文档。要了解如何在 GraphQL 中使用投影,请参阅选择集与投影。 |
要将 Spring Data 投影与 Querydsl 存储库一起使用,请创建投影接口或目标 DTO 类,并通过该projectAs
方法对其进行配置以获得
DataFetcher
生成目标类型:
class Account {
String name, identifier, description;
Person owner;
}
interface AccountProjection {
String getName();
String getIdentifier();
}
// For single result queries
DataFetcher<AccountProjection> dataFetcher =
QuerydslDataFetcher.builder(repository).projectAs(AccountProjection.class).single();
// For multi-result queries
DataFetcher<Iterable<AccountProjection>> dataFetcher =
QuerydslDataFetcher.builder(repository).projectAs(AccountProjection.class).many();
5.1.3. 自动注册
如果存储库使用 注释@GraphQlRepository
,则它会自动注册用于尚未注册DataFetcher
且返回类型与存储库域类型匹配的查询。这包括单值和多值查询。
默认情况下,查询返回的 GraphQL 类型的名称必须与存储库域类型的简单名称匹配。如果需要,您可以使用 的typeName
属性
@GraphQlRepository
来指定目标 GraphQL 类型名称。
自动注册检测给定存储库是否通过构建器方法实现QuerydslBinderCustomizer
并透明地应用它。QuerydslDataFetcher
自动注册是通过RuntimeWiringConfigurer
可以从QuerydslDataFetcher
. Boot starter会
自动检测@GraphQlRepository
bean 并使用它们来初始化
RuntimeWiringConfigurer
with。
自动注册不支持自定义。QueryByExampleDataFetcher
如果需要,您需要DataFetcher
使用
RuntimeWiringConfigurer
.
5.2. 示例查询
Spring Data 支持使用 Query by Example 来获取数据。Query by Example (QBE) 是一种简单的查询技术,不需要您通过特定于商店的查询语言编写查询。
首先声明一个存储库QueryByExampleExecutor
:
public interface AccountRepository extends Repository<Account, Long>,
QueryByExampleExecutor<Account> {
}
用于QueryByExampleDataFetcher
将存储库变成DataFecher
:
// For single result queries
DataFetcher<Account> dataFetcher =
QueryByExampleDataFetcher.builder(repository).single();
// For multi-result queries
DataFetcher<Iterable<Account>> dataFetcher =
QueryByExampleDataFetcher.builder(repository).many();
您现在可以DataFetcher
通过
RuntimeWiringConfigurer
.
使用DataFetcher
GraphQL 参数映射来创建存储库的域类型,并将其用作示例对象来获取数据。Spring Data 支持
QueryByExampleDataFetcher
JPA、MongoDB、Neo4j 和 Redis。
如果存储库是ReactiveQueryByExampleExecutor
,则构建器返回
DataFetcher<Mono<Account>>
或DataFetcher<Flux<Account>>
。Spring Data 支持 MongoDB、Neo4j、Redis 和 R2dbc 的这种变体。
5.2.2. 定制
QueryByExampleDataFetcher
支持接口和 DTO 投影来转换查询结果,然后返回这些结果以进行进一步的 GraphQL 处理。
要了解投影是什么,请参阅 Spring Data 文档。要了解投影在 GraphQL 中的作用,请参阅选择集与投影。 |
要将 Spring Data 投影与 Query by Example 存储库一起使用,请创建投影接口或目标 DTO 类,并通过该projectAs
方法对其进行配置以获得
DataFetcher
生成目标类型:
class Account {
String name, identifier, description;
Person owner;
}
interface AccountProjection {
String getName();
String getIdentifier();
}
// For single result queries
DataFetcher<AccountProjection> dataFetcher =
QueryByExampleDataFetcher.builder(repository).projectAs(AccountProjection.class).single();
// For multi-result queries
DataFetcher<Iterable<AccountProjection>> dataFetcher =
QueryByExampleDataFetcher.builder(repository).projectAs(AccountProjection.class).many();
5.2.3。自动注册
如果存储库使用 注释@GraphQlRepository
,则它会自动注册用于尚未注册DataFetcher
且返回类型与存储库域类型匹配的查询。这包括单值和多值查询。
默认情况下,查询返回的 GraphQL 类型的名称必须与存储库域类型的简单名称匹配。如果需要,您可以使用 的typeName
属性
@GraphQlRepository
来指定目标 GraphQL 类型名称。
自动注册是通过RuntimeWiringConfigurer
可以从QueryByExampleDataFetcher
. Boot starter会
自动检测@GraphQlRepository
bean 并使用它们来初始化
RuntimeWiringConfigurer
with。
自动注册不支持自定义。QueryByExampleDataFetcher
如果需要,您需要DataFetcher
使用
RuntimeWiringConfigurer
.
5.3. 选择集与投影
出现的一个常见问题是,GraphQL 选择集与 Spring Data 投影相比如何 ,它们各自扮演什么角色?
简短的回答是 Spring for GraphQL 不是将 GraphQL 查询直接转换为 SQL 或 JSON 查询的数据网关。相反,它允许您利用现有的 Spring 技术,并且不假设 GraphQL 模式和底层数据模型之间存在一对一的映射。这就是为什么数据模型的客户端驱动选择和服务器端转换可以发挥互补作用的原因。
为了更好地理解,请考虑 Spring Data 将域驱动(DDD)设计作为管理数据层复杂性的推荐方法。在 DDD 中,遵守聚合的约束很重要。根据定义,聚合仅在全部加载时才有效,因为部分加载的聚合可能会对聚合功能施加限制。
在 Spring Data 中,您可以选择是否希望您的聚合按原样公开,或者是否在将其作为 GraphQL 结果返回之前对数据模型应用转换。有时这样做就足够了,默认情况下, Querydsl和Query by Example集成将 GraphQL 选择集转换为基础 Spring Data 模块用来限制选择的属性路径提示。
在其他情况下,减少甚至转换底层数据模型以适应 GraphQL 模式很有用。Spring Data 通过 Interface 和 DTO Projections 支持这一点。
null
接口投影定义了一组固定的属性,以根据数据存储查询结果公开属性可能存在或不存在的位置。有两种接口投影,它们都决定从底层数据源加载哪些属性:
-
如果您无法部分实现聚合对象,但仍想公开属性子集,则封闭界面投影会很有帮助。
-
开放接口投影 利用 Spring 的
@Value
注释和 SpEL表达式来应用轻量级数据转换,例如连接、计算或将静态函数应用于属性。
DTO 投影提供了更高级别的自定义,因为您可以将转换代码放置在构造函数或 getter 方法中。
DTO 投影从查询中具体化,其中各个属性由投影本身确定。DTO 投影通常与全参数构造函数(例如 Java 记录)一起使用,因此只有在所有必需字段(或列)都是数据库查询结果的一部分时才能构造它们。
6.带注释的控制器
Spring for GraphQL 提供了一个基于注解的编程模型,其中@Controller
组件使用注解来声明具有灵活方法签名的处理程序方法,以获取特定 GraphQL 字段的数据。例如:
@Controller
public class GreetingController {
@QueryMapping (1)
public String hello() { (2)
return "Hello, world!";
}
}
1 | 将此方法绑定到查询,即查询类型下的字段。 |
2 | 如果未在注解上声明,则根据方法名称确定查询。 |
Spring for GraphQL 使用RuntimeWiring.Builder
将上述处理程序方法注册为
graphql.schema.DataFetcher
名为“hello”的查询。
6.1。宣言
您可以将@Controller
bean 定义为标准 Spring bean 定义。构造
@Controller
型允许自动检测,与 Spring 对检测类路径上的类和为它们自动注册 bean 定义的一般支持保持@Controller
一致@Component
。它还充当带注释类的原型,表明它在 GraphQL 应用程序中作为数据获取组件的角色。
AnnotatedControllerConfigurer
检测@Controller
bean 并将其带注释的处理程序方法注册为DataFetcher
s via RuntimeWiring.Builder
。它是一个RuntimeWiringConfigurer
可以添加到GraphQlSource.Builder
. Spring Boot starter 自动声明AnnotatedControllerConfigurer
为 bean 并将所有RuntimeWiringConfigurer
bean 添加到GraphQlSource.Builder
并启用对带注释DataFetcher
的 s 的支持,请参阅 Boot starter 文档中的
GraphQL RuntimeWiring部分。
6.2.@SchemaMapping
注释将@SchemaMapping
处理程序方法映射到 GraphQL 模式中的字段,并将其声明DataFetcher
为该字段的。注解可以指定父类型名称,以及字段名称:
@Controller
public class BookController {
@SchemaMapping(typeName="Book", field="author")
public Author getAuthor(Book book) {
// ...
}
}
注释也可以省略这些@SchemaMapping
属性,在这种情况下,字段名称默认为方法名称,而类型名称默认为注入方法的源/父对象的简单类名称。例如,下面默认键入“Book”和字段“author”:
@Controller
public class BookController {
@SchemaMapping
public Author author(Book book) {
// ...
}
}
@SchemaMapping
可以在类级别声明注解,为类中的所有处理程序方法指定默认类型名称。
@Controller
@SchemaMapping(typeName="Book")
public class BookController {
// @SchemaMapping methods for fields of the "Book" type
}
@QueryMapping
, @MutationMapping
, 和@SubscriptionMapping
是元注释,它们本身被注释@SchemaMapping
并且 typeName 分别预设为Query
,
Mutation
, 或Subscription
。实际上,这些是 Query、Mutation 和 Subscription 类型下字段的快捷注释。例如:
@Controller
public class BookController {
@QueryMapping
public Book bookById(@Argument Long id) {
// ...
}
@MutationMapping
public Book addBook(@Argument BookInput bookInput) {
// ...
}
@SubscriptionMapping
public Flux<Book> newPublications() {
// ...
}
}
@SchemaMapping
处理程序方法具有灵活的签名,可以从一系列方法参数和返回值中进行选择。
6.2.1. 方法签名
模式映射处理程序方法可以具有以下任何方法参数:
方法论据 | 描述 |
---|---|
|
用于访问绑定到更高级别的类型化对象的命名字段参数。见 |
|
用于访问绑定到更高级别的类型化对象的所有字段参数。见 |
|
用于通过项目接口访问字段参数。请参阅 |
资源 |
用于访问字段的源(即父/容器)实例。请参阅来源。 |
|
用于访问 |
|
|
|
|
|
用于从 |
|
从 Spring Security 上下文中获取(如果可用)。 |
|
用于 |
|
用于通过 访问查询的选择集 |
|
对于 |
|
用于直接访问底层 |
模式映射处理程序方法可以返回:
-
任何类型的解析值。
-
Mono
和Flux
异步值。支持控制器方法和ReactiveDataFetcher
中描述的任何方法。DataFetcher
-
java.util.concurrent.Callable
使值异步生成。为此,AnnotatedControllerConfigurer
必须使用Executor
.
6.2.2.@Argument
在 GraphQL Java 中,DataFetchingEnvironment
提供对特定于字段的参数值映射的访问。这些值可以是简单的标量值(例如 String、Long)、Map
用于更复杂输入的 a 个值或 aList
个值。
使用@Argument
注解将参数绑定到目标对象并注入处理程序方法。绑定是通过将参数值映射到预期方法参数类型的主数据构造函数来执行的,或者通过使用默认构造函数来创建对象,然后将参数值映射到其属性。这是递归重复的,使用所有嵌套的参数值并相应地创建嵌套的目标对象。例如:
@Controller
public class BookController {
@QueryMapping
public Book bookById(@Argument Long id) {
// ...
}
@MutationMapping
public Book addBook(@Argument BookInput bookInput) {
// ...
}
}
默认情况下,如果方法参数名称可用(需要-parameters
Java 8+ 的编译器标志或来自编译器的调试信息),则它用于查找参数。如果需要,您可以通过注解自定义名称,例如@Argument("bookInput")
.
@Argument 注释没有“必需”标志,也没有指定默认值的选项
。这两者都可以在 GraphQL 模式级别指定,并由 GraphQL Java 强制执行。
|
如果绑定失败,BindException
则会引发 a 绑定问题累积为字段错误,其中field
每个错误的 是发生问题的参数路径。
您可以@Argument
在Map<String, Object>
参数上使用,以获取所有参数值。@Argument
不得设置名称属性 on 。
6.2.3.@Arguments
@Arguments
如果要将完整的参数映射绑定到单个目标对象,请使用注释,@Argument
与绑定特定的命名参数相反。
例如,@Argument BookInput bookInput
使用参数“bookInput”的值来初始化BookInput
,同时@Arguments
使用完整的参数映射,在这种情况下,顶级参数绑定到BookInput
属性。
6.2.4. @ProjectedPayload
界面
作为使用完整对象的替代方法@Argument
,您还可以使用投影接口通过定义明确的最小接口访问 GraphQL 请求参数。
当 Spring Data 在类路径上时,参数投影由
Spring Data 的接口投影提供。
要利用这一点,请创建一个带有注释的接口@ProjectedPayload
并将其声明为控制器方法参数。如果参数用 注释@Argument
,则它适用于DataFetchingEnvironment.getArguments()
映射中的单个参数。如果没有声明@Argument
,则投影适用于完整参数映射中的顶级参数。
例如:
@Controller
public class BookController {
@QueryMapping
public Book bookById(BookIdProjection bookId) {
// ...
}
@MutationMapping
public Book addBook(@Argument BookInputProjection bookInput) {
// ...
}
}
@ProjectedPayload
interface BookIdProjection {
Long getId();
}
@ProjectedPayload
interface BookInputProjection {
String getName();
@Value("#{target.author + ' ' + target.name}")
String getAuthorAndName();
}
6.2.5。资源
在 GraphQL Java 中,DataFetchingEnvironment
提供对字段源(即父/容器)实例的访问。要访问它,只需声明预期目标类型的方法参数。
@Controller
public class BookController {
@SchemaMapping
public Author author(Book book) {
// ...
}
}
源方法参数还有助于确定映射的类型名称。如果 Java 类的简单名称与 GraphQL 类型匹配,则无需在@SchemaMapping
注解中显式指定类型名称。
给 |
6.2.6。DataLoader
当您为实体注册批量加载函数时,如
批量加载中所述,您可以DataLoader
通过声明类型的方法参数来访问实体DataLoader
并使用它来加载实体:
@Controller
public class BookController {
public BookController(BatchLoaderRegistry registry) {
registry.forTypePair(Long.class, Author.class).registerMappedBatchLoader((authorIds, env) -> {
// return Map<Long, Author>
});
}
@SchemaMapping
public CompletableFuture<Author> author(Book book, DataLoader<Long, Author> loader) {
return loader.load(book.getAuthorId());
}
}
默认情况下,BatchLoaderRegistry
使用值类型的完整类名(例如 的类名Author
)作为注册键,因此只需DataLoader
使用泛型类型声明方法参数即可提供足够的信息来将其定位在DataLoaderRegistry
. 作为后备,DataLoader
方法参数解析器还将尝试方法参数名称作为键,但通常这不是必需的。
请注意,对于许多加载相关实体的情况,其中@SchemaMapping
简单地委托给 a ,您可以使用下一节中描述的@BatchMappingDataLoader
方法来减少样板
文件。
6.2.7. 验证
找到javax.validation.Validator
bean 时,AnnotatedControllerConfigurer
启用
对带注释的控制器方法的Bean Validation的支持。通常,bean 的类型为LocalValidatorFactoryBean
.
Bean 验证允许您声明类型的约束:
public class BookInput {
@NotNull
private String title;
@NotNull
@Size(max=13)
private String isbn;
}
然后,您可以在方法调用之前注释控制器方法参数@Valid
以验证它:
@Controller
public class BookController {
@MutationMapping
public Book addBook(@Argument @Valid BookInput bookInput) {
// ...
}
}
如果在验证期间发生错误,则会ConstraintViolationException
引发 a。您可以使用异常解决链来决定如何将其转换为错误以包含在 GraphQL 响应中,从而将其呈现给客户端。
除了@Valid ,您还可以使用@Validated 允许指定验证组的 Spring。
|
Bean 验证对 、 和 @ProjectedPayload 方法参数很有用@Argument
,
@Arguments
但
更
普遍地适用于任何方法参数。
验证和 Kotlin 协程
Hibernate Validator 与 Kotlin Coroutine 方法不兼容,并且在自省其方法参数时失败。请参阅 spring-projects/spring-graphql#344(评论) 以获取相关问题的链接和建议的解决方法。 |
6.3.@BatchMapping
批量加载通过使用
org.dataloader.DataLoader
延迟加载单个实体实例来解决 N+1 选择问题,因此它们可以一起加载。例如:
@Controller
public class BookController {
public BookController(BatchLoaderRegistry registry) {
registry.forTypePair(Long.class, Author.class).registerMappedBatchLoader((authorIds, env) -> {
// return Map<Long, Author>
});
}
@SchemaMapping
public CompletableFuture<Author> author(Book book, DataLoader<Long, Author> loader) {
return loader.load(book.getAuthorId());
}
}
对于加载关联实体的直接情况,如上所示,该
@SchemaMapping
方法只做委托给DataLoader
. 这是可以通过@BatchMapping
方法避免的样板。例如:
@Controller
public class BookController {
@BatchMapping
public Mono<Map<Book, Author>> author(List<Book> books) {
// ...
}
}
以上成为批量加载函数,BatchLoaderRegistry
其中键是Book
实例,加载的值是它们的作者。此外,a
DataFetcher
也透明地绑定到author
type 的字段Book
,它只是委托给DataLoader
作者,给定它的源/父Book
实例。
要用作唯一键, |
默认情况下,字段名默认为方法名,而类型名默认为输入List
元素类型的简单类名。两者都可以通过注释属性进行自定义。类型名称也可以从类级别继承
@SchemaMapping
。
6.3.1. 方法签名
批量映射方法支持以下参数:
方法论据 | 描述 |
---|---|
|
源/父对象。 |
|
从 Spring Security 上下文中获取(如果可用)。 |
|
|
|
用于访问来自 的上下文,该上下文与来自 的 |
|
GraphQL Java 中可用的环境到
|
批量映射方法可以返回:
返回类型 | 描述 |
---|---|
|
以父对象为键,批量加载对象为值的映射。 |
|
一系列批量加载的对象,其顺序必须与传递给方法的源/父对象的顺序相同。 |
|
命令式变体,例如无需远程调用。 |
|
异步调用的命令式变体。为此,
|
7. 安全
可以使用 HTTP URL 安全性来保护Web GraphQL 端点的路径,以确保只有经过身份验证的用户才能访问它。但是,这并不能区分单个 URL 上此类共享端点上的不同 GraphQL 请求。
要应用更细粒度的安全性,请添加 Spring Security 注释,例如
@PreAuthorize
或@Secured
到涉及获取 GraphQL 响应的特定部分的服务方法。由于上下文传播旨在使安全性和其他上下文在数据获取级别可用,这应该可以工作。
Spring for GraphQL 存储库包含 Spring MVC和 WebFlux的示例。
8. 客户
Spring for GraphQL 包括通过 HTTP、WebSocket 和 RSocket 执行 GraphQL 请求的客户端支持。
8.1。GraphQlClient
GraphQlClient
是一个合约,它为 GraphQL 请求声明一个独立于底层传输的通用工作流。这意味着无论底层传输是什么,请求都使用相同的 API 执行,并且任何特定于传输的内容都是在构建时配置的。
要创建一个GraphQlClient
,您需要以下扩展之一:
每个都定义了一个Builder
与传输相关的选项。所有构建器都从一个通用的基础 GraphQlClient 扩展而来,并Builder
带有与所有扩展相关的选项。
一旦你有了一个GraphQlClient
,你就可以开始提出请求了。
8.1.1。HTTP
HttpGraphQlClient
使用
WebClient通过 HTTP 执行 GraphQL 请求。
WebClient webClient = ... ;
HttpGraphQlClient graphQlClient = HttpGraphQlClient.create(webClient);
HttpGraphQlClient
创建后,您可以开始
使用相同的 API执行请求,而与底层传输无关。如果您需要更改任何特定于传输的详细信息,请mutate()
在现有实例上使用HttpGraphQlClient
以创建具有自定义设置的新实例:
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...
8.1.2. 网络套接字
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执行请求,而与底层传输无关。如果您需要更改任何特定于传输的详细信息,请mutate()
在现有实例上使用WebSocketGraphQlClient
以创建具有自定义设置的新实例:
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...
拦截器
除了执行请求之外,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 请求,但请注意最多可以有一个 type 的拦截器WebSocketGraphQlClientInterceptor
。
8.1.3. 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执行请求,而与底层传输无关。
8.1.4. 建造者
GraphQlClient
Builder
为所有扩展的构建器定义了一个具有通用配置选项的父级。目前,它允许您配置:
-
DocumentSource
为来自文件的请求加载文档的策略 -
拦截执行的请求
8.2. 要求
一旦你有了GraphQlClient
,你就可以开始通过
retrieve()或execute()来执行请求
,前者只是后者的快捷方式。
8.2.1。取回
下面检索和解码查询的数据:
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"
。
FieldAccessException
如果给定路径不存在,或者字段值存在null
并且有错误,则可能导致解码。FieldAccessException
提供对响应和字段的访问:
Mono<Project> projectMono = graphQlClient.document(document)
.retrieve("project")
.toEntity(Project.class)
.onErrorResume(FieldAccessException.class, ex -> {
ClientGraphQlResponse response = ex.getResponse();
// ...
ResponseField field = ex.getField();
// ...
});
8.2.2. 执行
Retrieve只是从响应映射中的单个路径解码的快捷方式。如需更多控制,请使用该execute
方法并处理响应:
例如:
Mono<Project> projectMono = graphQlClient.document(document)
.execute()
.map(response -> {
if (!response.isValid()) {
// Request failure... (1)
}
ResponseField 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 | null 由其设置的字段DataFetcher |
4 | 解码给定路径的数据 |
8.2.3. 文件来源
请求的文档String
可以定义在局部变量或常量中,也可以通过代码生成的请求对象生成。
您还可以在类路径下创建带有扩展名.graphql
或.gql
下
"graphql-documents/"
的文档文件,并通过文件名引用它们。
例如,给定一个名为projectReleases.graphql
in
的文件src/main/resources/graphql-documents
,其内容为:
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 | 从“project.graphql”加载文档 |
2 | 提供变量值。 |
IntelliJ 的“JS GraphQL”插件支持带有代码完成的 GraphQL 查询文件。
您可以使用GraphQlClient
Builder自定义
DocumentSource
按名称加载文档。
8.3. 订阅请求
GraphQlClient
可以通过支持它的传输执行订阅。目前,只有 WebSocket 传输支持 GraphQL 流,因此您需要创建一个
WebSocketGraphQlClient。
8.3.1. 取回
要启动订阅流,使用retrieveSubscription
which 类似于
检索单个响应但返回响应流,每个响应都解码为一些数据:
Flux<String> greetingFlux = client.document("subscription { greetings }")
.retrieveSubscription("greeting")
.toEntity(String.class);
订阅流可能以:
-
SubscriptionErrorException
如果服务器以包含一个或多个 GraphQL 错误的显式“错误”消息结束订阅。该异常提供对从该消息解码的 GraphQL 错误的访问。 -
GraphQlTransportException
例如,WebSocketDisconnectedException
如果底层连接关闭或丢失,您可以使用retry
运算符重新建立连接并重新开始订阅。
8.3.2. 执行
检索只是从每个响应映射中的单个路径解码的快捷方式。如需更多控制,请使用该executeSubscription
方法并直接处理每个响应:
Flux<String> greetingFlux = client.document("subscription { greetings }")
.executeSubscription()
.map(response -> {
if (!response.isValid()) {
// Request failure...
}
ResponseField 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)
});
8.4. 拦截
您创建一个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();
9. 测试
Spring for GraphQL 为通过 HTTP、WebSocket 和 RSocket 测试 GraphQL 请求以及直接针对服务器进行测试提供了专门的支持。
要使用它,请添加spring-graphql-test
到您的构建中:
dependencies {
// ...
testImplementation 'org.springframework.graphql:spring-graphql-test:1.0.0'
}
9.1。GraphQlTester
GraphQlTester
是一个合约,它声明了一个用于测试 GraphQL 请求的通用工作流程,该工作流程独立于底层传输。这意味着无论底层传输是什么,都使用相同的 API 测试请求,并且在构建时配置任何特定于传输的内容。
要创建GraphQlTester
通过客户端执行请求的,您需要以下扩展之一:
要创建一个GraphQlTester
在服务器端执行测试的,没有客户端:
每个都定义了一个Builder
与传输相关的选项。所有构建器都从一个通用的基础 GraphQlTester 扩展而来,并Builder
带有与所有扩展相关的选项。
9.1.1。HTTP
HttpGraphQlTester
使用
WebTestClient通过 HTTP 执行 GraphQL 请求,有或没有实时服务器,具体取决于
WebTestClient
配置方式。
要在没有实时服务器的情况下在 Spring WebFlux 中进行测试,请指向声明 GraphQL HTTP 端点的 Spring 配置:
ApplicationContext context = ... ;
WebTestClient client =
WebTestClient.bindToApplicationContext(context)
.configureClient()
.baseUrl("/graphql")
.build();
HttpGraphQlTester tester = HttpGraphQlTester.create(client);
要在没有实时服务器的情况下在 Spring MVC 中进行测试,请使用以下方法执行相同操作MockMvcWebTestClient
:
ApplicationContext context = ... ;
WebTestClient client =
MockMvcWebTestClient.bindToApplicationContext(context)
.configureClient()
.baseUrl("/graphql")
.build();
HttpGraphQlTester tester = HttpGraphQlTester.create(client);
或者针对在端口上运行的实时服务器进行测试:
WebTestClient client =
WebTestClient.bindToServer()
.baseUrl("http://localhost:8080/graphql")
.build();
HttpGraphQlTester tester = HttpGraphQlTester.create(client);
HttpGraphQlTester
创建后,您可以开始
使用相同的 API执行请求,而与底层传输无关。如果您需要更改任何特定于传输的详细信息,请mutate()
在现有实例上使用HttpSocketGraphQlTester
以创建具有自定义设置的新实例:
HttpGraphQlTester tester = HttpGraphQlTester.builder(clientBuilder)
.headers(headers -> headers.setBasicAuth("joe", "..."))
.build();
// Use tester...
HttpGraphQlTester anotherTester = tester.mutate()
.headers(headers -> headers.setBasicAuth("peter", "..."))
.build();
// Use anotherTester...
9.1.2. 网络套接字
WebSocketGraphQlTester
通过共享的 WebSocket 连接执行 GraphQL 请求。它是使用 Spring WebFlux 中的
WebSocketClient构建的
,您可以按如下方式创建它:
String url = "http://localhost:8080/graphql";
WebSocketClient client = new ReactorNettyWebSocketClient();
WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(url, client).build();
WebSocketGraphQlTester
是面向连接和多路复用的。每个实例都为所有请求建立自己的单一共享连接。通常,您只希望每台服务器使用一个实例。
WebSocketGraphQlTester
创建后,您可以开始
使用相同的 API执行请求,而与底层传输无关。如果您需要更改任何特定于传输的详细信息,请mutate()
在现有实例上使用WebSocketGraphQlTester
以创建具有自定义设置的新实例:
URI url = ... ;
WebSocketClient client = ... ;
WebSocketGraphQlTester tester = WebSocketGraphQlTester.builder(url, client)
.headers(headers -> headers.setBasicAuth("joe", "..."))
.build();
// Use tester...
WebSocketGraphQlTester anotherTester = tester.mutate()
.headers(headers -> headers.setBasicAuth("peter", "..."))
.build();
// Use anotherTester...
WebSocketGraphQlTester
提供一种stop()
可用于关闭 WebSocket 连接的方法,例如在测试运行后。
9.1.3. RSocket
RSocketGraphQlTester
使用RSocketRequester
spring-messaging 通过 RSocket 执行 GraphQL 请求:
URI uri = URI.create("wss://localhost:8080/rsocket");
WebsocketClientTransport transport = WebsocketClientTransport.create(url);
RSocketGraphQlTester client = RSocketGraphQlTester.builder()
.clientTransport(transport)
.build();
RSocketGraphQlTester
是面向连接和多路复用的。每个实例为所有请求建立自己的单个共享会话。通常,您只希望每台服务器使用一个实例。您可以使用stop()
测试仪上的方法显式关闭会话。
RSocketGraphQlTester
创建后,您可以开始
使用相同的 API执行请求,而与底层传输无关。
9.1.4。GraphQlService
很多时候,在服务器端测试 GraphQL 请求就足够了,而无需使用客户端通过传输协议发送请求。要直接针对 a 进行测试
ExecutionGraphQlService
,请使用ExecutionGraphQlServiceTester
扩展:
GraphQlService service = ... ;
ExecutionGraphQlServiceTester tester = ExecutionGraphQlServiceTester.create(service);
ExecutionGraphQlServiceTester
创建后,您可以开始
使用相同的 API执行请求,而与底层传输无关。
9.1.5。WebGraphQlHandler
该GraphQlService
扩展允许您在服务器端进行测试,而无需客户端。但是,在某些情况下,使用给定的模拟传输输入涉及服务器端传输处理是有用的。
该WebGraphQlTester
扩展允许您
在传递给请求执行WebGraphQlInterceptor
之前通过链处理请求:ExecutionGraphQlService
WebGraphQlHandler handler = ... ;
WebGraphQlTester tester = WebGraphQlTester.create(handler);
此扩展的构建器允许您定义 HTTP 请求详细信息:
WebGraphQlHandler handler = ... ;
WebGraphQlTester tester = WebGraphQlTester.builder(handler)
.headers(headers -> headers.setBasicAuth("joe", "..."))
.build();
WebGraphQlServiceTester
创建后,您可以开始
使用相同的 API执行请求,而与底层传输无关。
9.2. 要求
一旦你有了GraphQlTester
,你就可以开始测试请求了。下面对项目执行查询并使用JsonPath从响应中提取项目发布版本:
String document = "{" +
" project(slug:\"spring-framework\") {" +
" releases {" +
" version" +
" }"+
" }" +
"}";
graphQlTester.document(document)
.execute()
.path("project.releases[*].version")
.entityList(String.class)
.hasSizeGreaterThan(1);
JsonPath 与响应的“数据”部分相关。
您还可以在类路径下创建带有扩展名.graphql
或.gql
下
"graphql-test/"
的文档文件,并通过文件名引用它们。
例如,给定一个名为projectReleases.graphql
in
的文件src/main/resources/graphql-test
,其内容为:
query projectReleases($slug: ID!) {
project(slug: $slug) {
releases {
version
}
}
}
然后你可以使用:
graphQlTester.documentName("projectReleases") (1)
.variable("slug", "spring-framework") (2)
.execute()
.path("project.releases[*].version")
.entityList(String.class)
.hasSizeGreaterThan(1);
1 | 请参阅名为“项目”的文件中的文档。 |
2 | 设置slug 变量。 |
IntelliJ 的“JS GraphQL”插件支持带有代码完成的 GraphQL 查询文件。 |
如果请求没有任何响应数据,例如突变,使用executeAndVerify
代替execute
来验证响应中没有错误:
graphQlTester.query(query).executeAndVerify();
有关错误处理的更多详细信息,请参阅错误。
9.3. 订阅
要测试订阅,请调用executeSubscription
而不是execute
获取响应流,然后使用StepVerifier
from Project Reactor 来检查流:
Flux<String> greetingFlux = tester.document("subscription { greetings }")
.executeSubscription()
.toFlux("greetings", String.class); // decode at JSONPath
StepVerifier.create(greetingFlux)
.expectNext("Hi")
.expectNext("Bonjour")
.expectNext("Hola")
.verifyComplete();
仅WebSocketGraphQlTester或服务器端
GraphQlService
和WebGraphQlHandler
扩展支持订阅。
9.4。错误
使用时verify()
,响应中“errors”键下的任何错误都会导致断言失败。要抑制特定错误,请在之前使用错误过滤器
verify()
:
graphQlTester.query(query)
.execute()
.errors()
.filter(error -> ...)
.verify()
.path("project.releases[*].version")
.entityList(String.class)
.hasSizeGreaterThan(1);
您可以在构建器级别注册错误过滤器,以应用于所有测试:
WebGraphQlTester graphQlTester = WebGraphQlTester.builder(client)
.errorFilter(error -> ...)
.build();
如果你想验证一个错误确实存在,并且与 相比filter
,如果不存在则抛出一个断言错误,然后使用exepect
:
graphQlTester.query(query)
.execute()
.errors()
.expect(error -> ...)
.verify()
.path("project.releases[*].version")
.entityList(String.class)
.hasSizeGreaterThan(1);
您还可以通过 a 检查所有错误Consumer
,这样做也将它们标记为已过滤,因此您还可以检查响应中的数据:
graphQlTester.query(query)
.execute()
.errors()
.satisfy(errors -> {
// ...
});
10. 样品
这个 Spring for GraphQL 存储库包含各种场景的示例应用程序。
您可以通过克隆此存储库并从 IDE 运行主应用程序类或在命令行中键入以下内容来运行它们:
$ ./gradlew :samples:{sample-directory-name}:bootRun