在一個schema上執行查詢,須要首先建立一個GraphQL
對象,而後調用該對象的execute()
方法java
GraphQL在執行結束後返回一個ExecutionResult
對象,其中包含查詢的數據(data字段)或錯誤信息(errors字段)。git
GraphQLSchema schema = GraphQLSchema.newSchema() .query(queryType) .build(); GraphQL graphQL = GraphQL.newGraphQL(schema) .build(); ExecutionInput executionInput = ExecutionInput.newExecutionInput().query("query { hero { name } }") .build(); ExecutionResult executionResult = graphQL.execute(executionInput); Object data = executionResult.getData(); List<GraphQLError> errors = executionResult.getErrors();
每一個GraphQL中的field(字段)都會綁定一個DataFetcher。在其餘的GraphQL實現中,也稱DataFetcher爲Resolver。github
通常,咱們可使用PropertyDataFetcher對象,從內存中的POJO對象中提取field的值。若是你沒有爲一個field顯式指定一個DataFetcher,那麼GraphQL默認會使用PropertyDataFetcher與該field進行綁定。
但對於最頂層的領域對象(domain object)查詢來講,你須要定義一個特定的data fetcher。頂層的領域對象查詢,可能會包含數據庫操做,或經過HTTP協議與其餘系統進行交互得到相應數據。
GraphQL - Java並不關心你是如何獲取領域對象數據的,這是業務代碼中須要考慮的問題。它也不關心在獲取數據時須要怎樣的認證方式,你須要在業務層代碼中實現這部分邏輯。數據庫
一個簡單的Data Fetcher示例以下:json
DataFetcher userDataFetcher = new DataFetcher() { @Override public Object get(DataFetchingEnvironment environment) { return fetchUserFromDatabase(environment.getArgument("userId")); } };
每一個DataFetcher的方法中,都會傳入一個DataFetchingEnvironment對象。這個對象中包含了當前正在被請求的field,field所關聯的請求參數argument,以及其餘信息(例如,當前field的上層field、當前查詢的root對象或當前查詢的context對象等)。
在上面的例子中,GraphQL會在data fetcher返回執行結果前一直等待,這是一種阻塞的調用方式。也能夠經過返回data相關的CompletionStage對象,將DataFetcher的調用異步化,實現異步調用。後端
若是在GraphQL的DataFetcher執行過程當中產生了異常,在GraphQL的執行策略下, 將生成一個ExceptioinWhileDataFetching錯誤對象,並將它添加到返回的ExecutionResult對象的errors列表字段當中。GraphQL容許返回部分紅功的數據,並帶上異常信息。promise
正常的異常處理邏輯以下:緩存
public class SimpleDataFetcherExceptionHandler implements DataFetcherExceptionHandler { private static final Logger log = LoggerFactory.getLogger(SimpleDataFetcherExceptionHandler.class); @Override public void accept(DataFetcherExceptionHandlerParameters handlerParameters) { Throwable exception = handlerParameters.getException(); SourceLocation sourceLocation = handlerParameters.getField().getSourceLocation(); ExecutionPath path = handlerParameters.getPath(); ExceptionWhileDataFetching error = new ExceptionWhileDataFetching(path, exception, sourceLocation); handlerParameters.getExecutionContext().addError(error); log.warn(error.getMessage(), exception); } }
若是拋出的異常是一個GraphqlError對象,那麼它會將異常信息和擴展屬性轉換到ExceptionWhileDataFetching對象。能夠把本身的錯誤信息,放到GraphQL的錯誤列表當中返回給調用方。
例如,假設data fetcher拋出了以下異常,那麼foo和fizz屬性會包含在graphql error對象當中。app
class CustomRuntimeException extends RuntimeException implements GraphQLError { @Override public Map<String, Object> getExtensions() { Map<String, Object> customAttributes = new LinkedHashMap<>(); customAttributes.put("foo", "bar"); customAttributes.put("fizz", "whizz"); return customAttributes; } @Override public List<SourceLocation> getLocations() { return null; } @Override public ErrorType getErrorType() { return ErrorType.DataFetchingException; } }
能夠編寫本身的DataFetcherExceptionHandler異常處理器改變它的行爲,只須要在執行策略中註冊一下。dom
例如,上述代碼記錄了底層的異常和調用棧信息,若是你不但願這些信息出如今輸出的錯誤列表中,能夠用一下的方法實現。
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中同時返回數據和多個error信息,只須要讓DataFetcher返回DataFetcherResult對象或CompletableFuture包裝後的DataFetcherResult對象便可。
在某些場景下,例如DataFetcher須要從多個數據源或其餘的GraphQL系統中獲取數據時,其中任一環節均可能產生錯誤。使用DataFetcherResult包含data和期間產生的全部error信息,比較常見。
下面的示例中,DataFetcher從另外的GraphQL系統中獲取數據,並返回執行的data和errors信息。
DataFetcher userDataFetcher = new DataFetcher() { @Override public Object get(DataFetchingEnvironment environment) { Map response = fetchUserFromRemoteGraphQLResource(environment.getArgument("userId")); List<GraphQLError> errors = response.get("errors")).stream() .map(MyMapGraphQLError::new) .collect(Collectors.toList(); return new DataFetcherResult(response.get("data"), errors); } };
一般,使用Jackson或GSON的json序列化庫,將返回查詢結果序列化爲json格式返回。然而對於如何序列化數據,序列化爲JSON後會保留哪些信息,取決於序列化庫自身。例如,對於null結果是否出如今序列化後的json數據當中,不一樣的序列化庫有不一樣的默認策略。須要手動指定json mapper來定義。
爲了保證能夠100%獲取一個符合graphql規範的json結果,能夠在返回結果result上調用toSpecification,而後將數據以json格式返回。
ExecutionResult executionResult = graphQL.execute(executionInput); Map<String, Object> toSpecificationResult = executionResult.toSpecification(); sendAsJson(toSpecificationResult);
首先,須要定義一個支持輸入參數的GraphQLObjectType
類型,該類型也是Mutation方法的參數類型。這些參數會在data fetcher調用時,更新GraphQL系統內部的領域數據信息(添加、修改或刪除)。
mutation的執行調用示例以下:
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) { createReview(episode: $ep, review: $review) { stars commentary } }
在mutation方法執行過程當中須要傳遞參數,例如實例中,須要傳遞$ep和$review變量。
在Java代碼中,可使用以下方式建立type,並綁定這個mutation操做。
GraphQLInputObjectType episodeType = newInputObject() .name("Episode") .field(newInputObjectField() .name("episodeNumber") .type(Scalars.GraphQLInt)) .build(); GraphQLInputObjectType reviewInputType = newInputObject() .name("ReviewInput") .field(newInputObjectField() .name("stars") .type(Scalars.GraphQLString) .name("commentary") .type(Scalars.GraphQLString)) .build(); GraphQLObjectType reviewType = newObject() .name("Review") .field(newFieldDefinition() .name("stars") .type(GraphQLString)) .field(newFieldDefinition() .name("commentary") .type(GraphQLString)) .build(); GraphQLObjectType createReviewForEpisodeMutation = newObject() .name("CreateReviewForEpisodeMutation") .field(newFieldDefinition() .name("createReview") .type(reviewType) .argument(newArgument() .name("episode") .type(episodeType) ) .argument(newArgument() .name("review") .type(reviewInputType) ) ) .build(); GraphQLCodeRegistry codeRegistry = newCodeRegistry() .dataFetcher( coordinates("CreateReviewForEpisodeMutation", "createReview"), mutationDataFetcher() ) .build(); GraphQLSchema schema = GraphQLSchema.newSchema() .query(queryType) .mutation(createReviewForEpisodeMutation) .codeRegistry(codeRegistry) .build();
注意,輸入參數只能是GraphQLInputObjectType類型,不能是能夠做爲輸出類型的GraphQLObjectType。
另外,Scalar類型比較特殊,能夠同時做爲輸入參數類型和輸出類型。
Mutation操做綁定的data fetcher能夠執行這個mutation,並返回輸出類型的數據信息:
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 http://facebook.github.io/graphql/October2016/#sec-Input-Objects // Map<String, Object> episodeInputMap = environment.getArgument("episode"); Map<String, Object> reviewInputMap = environment.getArgument("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類型對象返回給mutation的調用方。
graphql-java使用了徹底異步化的執行策略,調用executeAsync()後,返回CompleteableFuture
對象
GraphQL graphQL = buildSchema(); ExecutionInput executionInput = ExecutionInput.newExecutionInput().query("query { hero { name } }") .build(); CompletableFuture<ExecutionResult> promise = graphQL.executeAsync(executionInput); promise.thenAccept(executionResult -> { // here you might send back the results as JSON over HTTP encodeResultToJsonAndSendResponse(executionResult); }); promise.join();
使用CompletableFuture對象,能夠指定該執行結果結束後須要出發的後續行爲或操做,最後調用.join()方法等待執行完成。
實際上,使用GraphQL Java執行execute的同步操做,也是在調用異步的executeAsync方法以後,再調用join方法實現的。
ExecutionResult executionResult = graphQL.execute(executionInput); // the above is equivalent to the following code (in long hand) CompletableFuture<ExecutionResult> promise = graphQL.executeAsync(executionInput); ExecutionResult executionResult2 = promise.join();
若是DataFetcher返回了CompletableFuture
下面的代碼中使用了Java中的ForkJoinPool.commonPool線程池,提供異步執行操做流程。
DataFetcher userDataFetcher = new DataFetcher() { @Override public Object get(DataFetchingEnvironment environment) { CompletableFuture<User> userPromise = CompletableFuture.supplyAsync(() -> { return fetchUserViaHttp(environment.getArgument("userId")); }); return userPromise; } };
上述代碼在Java8中也能夠重構以下:
DataFetcher userDataFetcher = environment -> CompletableFuture.supplyAsync( () -> fetchUserViaHttp(environment.getArgument("userId")));
Graphql - Java會保證全部的CompletableFuture對象組合執行,並依照GraphQL規範返回執行結果。
在GraphQL - Java中也可使用AsyncDataFetcher.async(DataFetcher
DataFetcher userDataFetcher = async(environment -> fetchUserViaHttp(environment.getArgument("userId")));
在執行query或mutation時,GraphQL Java引擎會使用ExecutionStrategy接口的具體實現類(執行策略)。GraphQL - Java提供了一些策略,你也能夠編寫本身的執行策略。
能夠在建立GraphQL
對象時中綁定執行策略。
GraphQL.newGraphQL(schema) .queryExecutionStrategy(new AsyncExecutionStrategy()) .mutationExecutionStrategy(new AsyncSerialExecutionStrategy()) .build();
實際上,上述代碼等價於不指定ExecutionStrategy的默認策略。
query操做的默認執行策略是AsyncExecutionStrategy。在這個執行策略下,GraphQL Java引擎將field的返回結果包裝爲CompleteableFuture對象(若是返回結果自己爲CompletableFuture對象,則不處理),哪一個field的值獲取操做先完成並不重要。
若data fetcher調用自己返回的就是CompletationStage類型,則能夠最大化異步調用的性能。
對於以下的query:
query { hero { enemies { name } friends { name } } }
AsyncExecutionStrategy會在獲取friends字段值的同時,調用獲取enemies字段值的方法。而不會在獲取enemies以後獲取friends字段的值,以提高效率。
在執行結束後,GraphQL Java會將查詢結果按照請求的順序進行整合。查詢結果遵循Graphql規範,而且返回的field對象按照查詢的field字段的順序返回。
執行過程當中,僅僅是field字段的執行順序是任意的,返回的查詢結果依然是順序的。
GraphQL 規範要求mutation操做必須按照query的field順序依次執行。
所以,AsyncSerialExecutionStrategy是mutation的默認策略,而且它會保證每一個field在下一個field操做開始以前完成。
你仍然能夠在mutation類型的data fetcher中返回CompletionStage,但它們只會順序依次執行。
略。
在GraphQL Java執行查詢以前,查詢語句首先應該進行解析和驗證,這個過程有時候會很是耗時。
爲了不重複遍歷、驗證查詢語句,GraphQL.Builder容許引入PreparedDocumentProvider,來重用相同query語句的Document解析實例。
這個過程只是對Document進行緩存,並未對查詢的執行結果進行緩存。
Cache<String, PreparsedDocumentEntry> cache = Caffeine.newBuilder().maximumSize(10_000).build(); (1) GraphQL graphQL = GraphQL.newGraphQL(StarWarsSchema.starWarsSchema) .preparsedDocumentProvider(cache::get) (2) .build();
若開啓了緩存,那麼查詢語句中不能顯式的拼接查詢條件的值。而應該以變量的方式進行傳遞。例如:
query HelloTo { sayHello(to: "Me") { greeting } }
這個查詢語句,重寫以下:
query HelloTo($to: String!) { sayHello(to: $to) { greeting } } # 傳入參數以下: { "to": "Me" }