java execution

阅读: 评论:0

java execution

java execution

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小时内删除。

标签:java   execution
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23