Graphql的执行
查询(Queries)
要执行一个符合schema定义的查询,首先需通过schema对象创建一个GraphQL对象,然后调用execute()。
查询的结果是一个 ExecutionResult 对象,它包含查询数据列表或者是错误信息。
GraphQLSchema schema = wSchema()
.query(queryType)
.build();
GraphQL graphQL = wGraphQL(schema)
.build();
ExecutionInput executionInput = wExecutionInput().query("query { hero { name } }")
.build();
ExecutionResult executionResult = ute(executionInput);
Object data = Data();
List errors = Errors();
在StarWars查询测试中可以找到更复杂的查询示例
数据提取器 (Data Fetchers)
每个graphql字段类型都有一个graphql.schema.DataFetcher关联。
通常,如果您未在字段上指定DataFetcher,则graphql将使用默认的DataFetcher graphql.schema.PropertyDataFetcher从POJO对象中获取相应的属性值赋给graphql字段。
创建一个DataFetcher对象需要你自己去实现数据获取过程的代码,graphql-java 对如何获取数据对象,以及数据权限等等不关心,这是你需要关心的问题(可以和Java中接口概念作类比),这可能涉及进行数据库调用或通过HTTP语句联系另一个系统。
DataFetcher示例如下:
DataFetcher userDataFetcher = new DataFetcher() {
@Override
public Object get(DataFetchingEnvironment environment) {
return Argument("userId"));
}
};
每个DataFetcher对象都传递一个graphql.schema.DataFetchingEnvironment对象,该对象包含要获取的字段、向该字段提供了哪些参数以及其他信息,例如字段的父对象,查询根对象或查询上下文对象。
在上面的示例中,执行是同步的,它将等待一个DataFetcher返回数据后再继续。您可以在DataFetcher中通过返回一个CompletionStage对象到数据来达到执行异步的效果,这将在本文中进一步说明。
数据提取时的异常
如果在DataFetcher调用期间发生异常,默认情况下,执行策略将产生一个 graphql.ExceptionWhileDataFetching错误并将其添加到结果错误列表中。请记住,graphql允许有携带错误信息的部分结果。
下面是一段标准行为的示例代码。
public class SimpleDataFetcherExceptionHandler implements DataFetcherExceptionHandler {
private static final Logger log = Logger(SimpleDataFetcherExceptionHandler.class);
@Override
public void accept(DataFetcherExceptionHandlerParameters handlerParameters) {
Throwable exception = Exception();
SourceLocation sourceLocation = Field().getSourceLocation();
ExecutionPath path = Path();
ExceptionWhileDataFetching error = new ExceptionWhileDataFetching(path, exception, sourceLocation);
log.Message(), exception);
}
}
如果您抛出的异常本身是一个GraphqlError,则它将会把消息和自定义扩展属性从该异常传输到ExceptionWhileDataFetching对象中。这使您可以将自己的自定义属性放入GraphqlError中发送回给DataFetcher的调用方。
例如,假设您的DataFetcher引发了此异常。在foo和fizz属性将包含在最终的GraphqlError中:
class CustomRuntimeException extends RuntimeException implements GraphQLError {
@Override
public Map getExtensions() {
Map customAttributes = new LinkedHashMap<>();
customAttributes.put("foo", "bar");
customAttributes.put("fizz", "whizz");
return customAttributes;
}
@Override
public List getLocations() {
return null;
}
@Override
public ErrorType getErrorType() {
return ErrorType.DataFetchingException;
}
}
您可以通过创建自己的ution.DataFetcherExceptionHandler异常处理代码并将其提供给执行策略来更改此行为。
例如,上面的代码记录了潜在的异常和堆栈跟踪。某些人可能不希望在输出错误列表中看到它。因此,您可以使用此机制来更改该行为。
DataFetcherExceptionHandler handler = new DataFetcherExceptionHandler() {
@Override
public void accept(DataFetcherExceptionHandlerParameters handlerParameters) {
// // do your custom handling here. The parameters have all you need }
};
ExecutionStrategy executionStrategy = new AsyncExecutionStrategy(handler);
数据或错误的返回
DataFetcher可以通过ution.DataFetcherResult直接返回数据,可以同时返回数据和多个错误,或者把数据包装在CompletableFuture实例中返回来达到异步执行效果。当DataFetcher可能需要从多个来源或另一个GraphQL资源检索数据时,此功能很有用。
在此示例中,DataFetcher从另一个GraphQL资源中检索用户并返回其数据和错误。
DataFetcher userDataFetcher = new DataFetcher() {
@Override
public Object get(DataFetchingEnvironment environment) {
Map response = Argument("userId"));
List errors = ("errors")).stream()
.map(MyMapGraphQLError::new)
.List();
return new ("data"), errors);
}
};
将结果序列化为JSON
调用graphql的最常见做法是通过HTTP调用并返回JSON格式数据。因此,您需要将graphql.ExecutionResult对象转变成 JSON。
常用的转变方法是使用JSON序列化库,例如Jackson或GSON。但是,它们对数据结果的格式有它们特定要求。例如nulls,在graphql结果中很重要,因此您必须确保所使用的json映射器能够解析这样的数据。
为了确保您获得100%符合graphql规范的JSON结果,您应该调用toSpecification方法,然后将其作为JSON发送回去。
ExecutionResult executionResult = ute(executionInput);
Map toSpecificationResult = Specification();
sendAsJson(toSpecificationResult);
编辑修改(Mutations)
本质上,您还是需要定义一个修改(mutation)的GraphQLObjectType(参考有关schema的章节),这个GraphQLObjectType一般需定义输入的参数,这些参数是您在DataFetcher中进行修改操作或实现修改的业务逻辑所需要的。
修改是通过类似以下查询的方式调用的:
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
您需要在该修改操作期间传参数,在上面例子中,您需要为变量$ep和变量$review提供值。
您还可以通过创建类似下面这样的代码来实现此修改操作:
GraphQLInputObjectType episodeType = wInputObject()
.name("Episode")
.field(newInputObjectField()
.name("episodeNumber")
.type(Scalars.GraphQLInt))
.build();
GraphQLInputObjectType reviewInputType = wInputObject()
.name("ReviewInput")
.field(newInputObjectField()
.name("stars")
.type(Scalars.GraphQLString)
.name("commentary")
.type(Scalars.GraphQLString))
.build();
GraphQLObjectType reviewType = wObject()
.name("Review")
.field(newFieldDefinition()
.name("stars")
.type(GraphQLString))
.field(newFieldDefinition()
.name("commentary")
.type(GraphQLString))
.build();
GraphQLObjectType createReviewForEpisodeMutation = wObject()
.name("CreateReviewForEpisodeMutation")
.field(newFieldDefinition()
.name("createReview")
.type(reviewType)
.argument(newArgument()
.name("episode")
.type(episodeType)
)
.argument(newArgument()
.name("review")
.type(reviewInputType)
)
)
.build();
GraphQLCodeRegistry codeRegistry = wCodeRegistry()
.dataFetcher(
mutationDataFetcher()
)
.build();
GraphQLSchema schema = wSchema()
.query(queryType)
.mutation(createReviewForEpisodeMutation)
.codeRegistry(codeRegistry)
.build();
请注意,输入参数的类型一般为GraphQLInputObjectType,这个很重要,输入参数不能使用输出类型,例如GraphQLObjectType。不过,标量类型可以作为输入参数的类型,也可以作为输出类型。
此处的DataFetcher程序负责执行修改操作并返回一些相应的输出值:
private DataFetcher mutationDataFetcher() {
return new DataFetcher() {
@Override
public Review get(DataFetchingEnvironment environment) {
// // The graphql specification dictates that input object arguments MUST // be maps. You can convert them to POJOs inside the data fetcher if that // suits your code better // // See // Map episodeInputMap = Argument("episode");
Map reviewInputMap = Argument("review");
// // in this case we have type safe Java objects to call our backing code with // EpisodeInput episodeInput = EpisodeInput.fromMap(episodeInputMap);
ReviewInput reviewInput = ReviewInput.fromMap(reviewInputMap);
// make a call to your store to mutate your database Review updatedReview = reviewStore().update(episodeInput, reviewInput);
// this returns a new view of the data return updatedReview;
}
};
}
请注意它是如何调用数据存储以更改数据库的,然后将一个Review对象作为输出值,或者叫返回值。
异步执行
graphql-java在执行查询时使用完全异步执行技术。你可以像下面的代码一样,通过调用 executeAsync()得到一个CompleteableFuture对象结果:
GraphQL graphQL = buildSchema();
ExecutionInput executionInput = wExecutionInput().query("query { hero { name } }")
.build();
CompletableFuture promise = uteAsync(executionInput);
promise.thenAccept(executionResult -> {
// here you might send back the results as JSON over HTTP encodeResultToJsonAndSendResponse(executionResult);
});
promise.join();
CompletableFuture对象的作用就是,使您可以把执行完成后将要应用的操作和功能先组合起来。最后调用一个.join()等待执行。
实际上,在幕后,graphql-java引擎使用异步执行,并.execute()通过为您调用join使方法显得同步。因此以下代码实际上是相同的。
ExecutionResult executionResult = ute(executionInput);
// 上面的代码和下面的代码效果是一样的 (这里的代码都只是示意代码)
CompletableFuture promise = uteAsync(executionInput);
ExecutionResult executionResult2 = promise.join();
如果graphql.schema.DataFetcher返回了一个CompletableFuture对象,则这个对象将组合进整个的异步查询执行中。这意味着您可以并行触发多个字段获取请求。究竟使用哪种线程策略取决于DataFetcher的程序代码。
以下代码使用标准urrent.ForkJoinPoolmonPool()线程执行程序在另一个线程中提供值。
DataFetcher userDataFetcher = new DataFetcher() {
@Override
public Object get(DataFetchingEnvironment environment) {
CompletableFuture userPromise = CompletableFuture.supplyAsync(() -> {
return Argument("userId"));
});
return userPromise;
}
};
上面的代码以长格式编写,使用Java 8 lambda表达式,可以更简洁地编写如下
DataFetcher userDataFetcher = environment -> CompletableFuture.supplyAsync(
() -> Argument("userId")));
graphql-java引擎确保将所有CompletableFuture对象组合在一起,以提供遵循graphql规范的执行结果。
graphql-java中有一个有用的快捷方式来创建异步DataFetcher程序。使用graphql.schema.AsyncDataFetcher.async(DataFetcher)包裹一个 DataFetcher。可以将其与静态导入一起使用,以产生更具可读性的代码。
DataFetcher userDataFetcher = async(environment -> Argument("userId")));
执行策略(Execution Strategies)
一个派生自ution.ExecutionStrategy的策略类,可以用于运行查询或修改。graphql-java提供了许多不同的策略,如果您真的很热衷策略,甚至可以编写自己的策略。
您可以在创建GraphQL对象时确定要使用的执行策略。
.queryExecutionStrategy(new AsyncExecutionStrategy())
.mutationExecutionStrategy(new AsyncSerialExecutionStrategy())
.build();
实际上,上面的代码等效于默认设置,并且在大多数情况下是执行策略的非常明智的选择。
异步执行策略(AsyncExecutionStrategy)
默认情况下,“查询”的执行策略是ution.AsyncExecutionStrategy,它将每个字段分配为CompleteableFuture对象,但不关心哪个字段最先完成。此策略可实现最佳性能。
被调用的DataFetcher程序本身可以返回CompletionStage值,这将创建完全异步的行为。
因此,想象一个查询如下
query {
hero {
enemies {
name
}
friends {
name
}
}
}
该AsyncExecutionStrategy将同时自由的调度enemies和friends的字段。而不是先完成enemies的调度,然后再调度friends,这将是效率较低的。
但是它将按顺序组合结果。查询结果将遵循graphql规范,并返回按查询字段顺序组合的对象值。只是数据获取的执行顺序可以任意自由进行。
graphql规范中允许这种行为,并且实际上是被积极鼓励的。 用于只读查询。
详细信息,请参见specification 。
异步串行执行策略(AsyncSerialExecutionStrategy)
graphql规范指出,修改的执行必须按查询字段出现的顺序执行。
因此,修改(mutations)的执行策略默认是ution.AsyncSerialExecutionStrategy,它确保一个字段执行完成后才能执行下一个字段。您仍然可以在修改的DataFetcher中用CompletionStage返回数据对象,但是它们将被串行执行,并在调用下一个修改字段的DataFetcher之前完成。
订阅执行策略(SubscriptionExecutionStrategy)
Graphql订阅允许您创建对graphql数据的有状态订阅。SubscriptionExecutionStrategy 将会被用作执行策略,因为它具有对反应流API的支持。
有关反应式Publisher和Subscriber接口的更多信息,请参见/。
另请参阅订阅页面,以获取有关如何编写基于订阅的graphql服务的更多详细信息。
查询缓存(Query Caching)
在graphql-java引擎执行查询之前,会先对其进行解析和验证,此过程可能会很耗时。
为了避免需要重新解析/验证,GraphQL.Builder允许的实例PreparsedDocumentProvider重用Document实例。
请注意,这不会缓存查询结果,仅缓存已解析的Document。
Cache cache = wBuilder().maximumSize(10_000).build(); (1)
PreparsedDocumentProvider preparsedCache = PreparsedDocumentProvider {
@Override
public PreparsedDocumentEntry getDocument(ExecutionInput executionInput, Function computeFunction) {
Function mapCompute = key -> computeFunction.apply(executionInput);
(Query(), mapCompute);
}
}
GraphQL graphQL = wGraphQL(StarWarsSchema.starWarsSchema)
.preparsedDocumentProvider(preparsedCache) (2)
.build();创建首选缓存实例的实例,这里使用Caffeine 作为高质量的缓存解决方案。缓存实例应该是线程安全的并且是共享的。
PreparsedDocumentProvider是仅具有getDocument方法的接口,它的作用是获得一个预解析的查询缓存,如果不存在,则computeFunction被调用去解析和验证该查询。
为了获得较高的高速缓存命中率,建议将字段参数作为变量而不是直接在查询中传递。
以下查询:
query HelloTo {
sayHello(to: "Me") {
greeting
}
}
应该改写为
query HelloTo($to: String!) {
sayHello(to: $to) {
greeting
}
}
带有变量:
{
"to": "Me"
}
现在,无论提供了什么变量值,都可以重用该查询。
更新时间 1 Jan 2021
本文发布于:2024-01-31 09:30:31,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170666463427544.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |