Merge branch '1.2.x'

This commit is contained in:
rstoyanchev
2024-03-22 17:56:00 +00:00
4 changed files with 91 additions and 6 deletions

View File

@@ -312,7 +312,8 @@ In Spring GraphQL you can register a `PreparsedDocumentProvider` through
GraphQlSource.Builder builder = ...
// Create provider
PreparsedDocumentProvider provider = ...
PreparsedDocumentProvider provider =
new ApolloPersistedQuerySupport(new InMemoryPersistedQueryCache(Collections.emptyMap()));
builder.schemaResources(..)
.configureRuntimeWiring(..)
@@ -323,6 +324,38 @@ The xref:request-execution.adoc#execution.graphqlsource[GraphQlSource section] e
[[execution.thread-model]]
== Thread Model
Most GraphQL requests benefit from concurrent execution in fetching nested fields. This is
why most applications today rely on GraphQL Java's `AsyncExecutionStrategy`, which allows
data fetchers to return `CompletionStage` and to execute concurrently rather than serially.
Java 21 and virtual threads add an important ability to use more threads efficiently, but
it is still necessary to execute concurrently rather than serially in order for request
execution to complete more quickly.
Spring for GraphQL supports:
- <<execution.reactive-datafetcher, Reactive data fetchers>>, and those are
adapted to `CompletionStage` as expected by `AsyncExecutionStrategy`.
- `CompletionStage` as return value.
- Controller methods that are Kotlin coroutine methods.
- xref:controllers.adoc#controllers.schema-mapping[@SchemaMapping] and
xref:controllers.adoc#controllers.schema-mapping[@BatchMapping] methods can return
`Callable` that is submitted to an `Executor` such as the Spring Framework
`VirtualThreadTaskExecutor`. To enable this, you must configure an `Executor` on
`AnnotatedControllerConfigurer`.
Spring for GraphQL runs on either Spring MVC or WebFlux as the transport. Spring MVC
uses async request execution, unless the resulting `CompletableFuture` is done
immediately after the GraphQL Java engine returns, which would be the case if the
request is simple enough and did not require asynchronous data fetching.
[[execution.reactive-datafetcher]]
== Reactive `DataFetcher`
@@ -342,9 +375,10 @@ xref:request-execution.adoc#execution.context.webflux[WebFlux Context].
== Context Propagation
Spring for GraphQL provides support to transparently propagate context from the
xref:transports.adoc#server.transports.http[HTTP], through GraphQL Java, and to `DataFetcher` and other components it
invokes. This includes both `ThreadLocal` context from the Spring MVC request handling
thread and Reactor `Context` from the WebFlux processing pipeline.
xref:transports.adoc#server.transports.http[HTTP] transport, through GraphQL Java, and to
`DataFetcher` and other components it invokes. This includes both `ThreadLocal` context
from the Spring MVC request handling thread and Reactor `Context` from the WebFlux
processing pipeline.
[[execution.context.webmvc]]

View File

@@ -17,6 +17,8 @@ package org.springframework.graphql.server.support;
import java.util.Map;
import graphql.execution.preparsed.persisted.PersistedQuerySupport;
import org.springframework.graphql.GraphQlRequest;
import org.springframework.lang.Nullable;
import org.springframework.web.server.ServerWebInputException;
@@ -85,6 +87,9 @@ public class SerializableGraphQlRequest implements GraphQlRequest {
@Override
public String getDocument() {
if (this.query == null) {
if (this.extensions != null && this.extensions.get("persistedQuery") != null) {
return PersistedQuerySupport.PERSISTED_QUERY_MARKER;
}
throw new ServerWebInputException("No 'query'");
}
return this.query;

View File

@@ -22,10 +22,11 @@ import java.util.List;
import java.util.Locale;
import java.util.UUID;
import jakarta.servlet.ServletException;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import graphql.execution.preparsed.persisted.ApolloPersistedQuerySupport;
import graphql.execution.preparsed.persisted.InMemoryPersistedQueryCache;
import jakarta.servlet.ServletException;
import org.junit.jupiter.api.Test;
import org.springframework.context.i18n.LocaleContextHolder;
@@ -109,6 +110,45 @@ public class GraphQlHttpHandlerTests {
assertThatNoException().isThrownBy(() -> UUID.fromString(id));
}
@Test
void persistedQuery() throws Exception {
ApolloPersistedQuerySupport documentProvider =
new ApolloPersistedQuerySupport(new InMemoryPersistedQueryCache(Collections.emptyMap()));
GraphQlHttpHandler handler = GraphQlSetup.schemaContent("type Query { greeting: String }")
.configureGraphQl(builder -> builder.preparsedDocumentProvider(documentProvider))
.toHttpHandler();
String document = """
{
"query" : "{__typename}",
"extensions": {
"persistedQuery": {
"version":1,
"sha256Hash":"ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38"
}
}
}""";
MockHttpServletResponse servletResponse = handleRequest(createServletRequest(document, "*/*"), handler);
assertThat(servletResponse.getContentAsString()).isEqualTo("{\"data\":{\"__typename\":\"Query\"}}");
document = """
{
"extensions":{
"persistedQuery":{
"version":1,
"sha256Hash":"ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38"
}
}
}""";
servletResponse = handleRequest(createServletRequest(document, "*/*"), handler);
assertThat(servletResponse.getContentAsString()).isEqualTo("{\"data\":{\"__typename\":\"Query\"}}");
}
private MockHttpServletRequest createServletRequest(String query, String accept) {
MockHttpServletRequest servletRequest = new MockHttpServletRequest("POST", "/");
servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE);

View File

@@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import graphql.GraphQL;
import graphql.execution.instrumentation.Instrumentation;
@@ -146,6 +147,11 @@ public class GraphQlSetup implements GraphQlServiceSetup {
return this;
}
public GraphQlSetup configureGraphQl(Consumer<GraphQL.Builder> configurer) {
this.graphQlSourceBuilder.configureGraphQl(configurer);
return this;
}
public GraphQL toGraphQl() {
return this.graphQlSourceBuilder.build().graphQl();
}