Document GraalVM Native support

This commit documents the GraalVM Native image support in Spring for
GraphQL and lists relevant pointers to the Spring Framework and Spring
Boot documentations.

This also explains how developers are expected to provide reachability
hints for the cases where the AOT processing is not able to discover the
relevant types.

Closes gh-581
This commit is contained in:
Brian Clozel
2022-12-16 10:24:34 +01:00
parent decbc40e48
commit acf1f30c40
11 changed files with 273 additions and 5 deletions

View File

@@ -6,7 +6,7 @@ ext {
moduleProjects = [project(":spring-graphql"), project(":spring-graphql-test")]
springFrameworkVersion = "6.0.3"
graphQlJavaVersion = "19.2"
bootVersion = "3.0.0"
springBootVersion = "3.0.0"
}
description = "Spring for GraphQL"

View File

@@ -23,6 +23,7 @@ dependencies {
api 'org.springframework:spring-webmvc'
api 'org.springframework:spring-websocket'
api 'org.springframework:spring-messaging'
api 'org.springframework.data:spring-data-commons'
asciidoctorExtensions 'io.spring.asciidoctor.backends:spring-asciidoctor-backends:0.0.3'
}
@@ -47,7 +48,7 @@ repositories {
ext.javadocLinks = [
"https://docs.oracle.com/javase/8/docs/api/",
"https://javadoc.io/doc/com.graphql-java/graphql-java/${graphQlJavaVersion}/",
"https://docs.spring.io/spring-boot/docs/${bootVersion}/api/",
"https://docs.spring.io/spring-boot/docs/${springBootVersion}/api/",
"https://docs.spring.io/spring-framework/docs/5.3.x/javadoc-api/"
] as String[]
@@ -102,7 +103,8 @@ asciidoctor {
}
outputDir "$buildDir/docs/reference/html"
attributes 'spring-graphql-version': project.version,
'spring-boot-version': bootVersion
'spring-boot-version': springBootVersion,
'spring-framework-version': springFrameworkVersion
}
asciidoctor.mustRunAfter "check"

View File

@@ -21,6 +21,6 @@
:github-wiki: https://github.com/{github-repo}/wiki
:graphql-java-docs: https://www.graphql-java.com/documentation
:javadoc: https://docs.spring.io/spring-graphql/docs/{spring-graphql-version}/api
:spring-framework-ref-docs: https://docs.spring.io/spring-framework/docs/current/reference/html
// {spring-boot-version} attribute from build.gradle
// version attributes from main build.gradle
:spring-framework-ref-docs: https://docs.spring.io/spring-framework/docs/{spring-framework-version}/reference/html
:spring-boot-ref-docs: https://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference/html

View File

@@ -0,0 +1,56 @@
[[graalvm]]
= GraalVM Native support
Spring Framework 6.0 introduced the support infrastructure for compiling Spring applications to https://www.graalvm.org/22.3/reference-manual/native-image/[GraalVM Native images].
If you are not familiar with GraalVM in general, how this differs from applications deployed on the JVM and what it means for Spring application,
please refer to the dedicated {spring-boot-ref-docs}/native-image.html#native-image[Spring Boot 3.0 GraalVM Native Image support documentation].
Spring Boot also documents the https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-with-GraalVM[know limitations with the GraalVM support in Spring].
[[graalvm.graphql-java]]
== GraphQL Java metadata
Since the {spring-boot-ref-docs}/native-image.html#native-image.introducing-graalvm-native-images.key-differences-with-jvm-deployments[static analysis of your application is done at build time],
GraalVM might need extra hints if your application is looking up static resources, performing reflection or creating JDK proxies at runtime.
GraphQL Java is performing three tasks at runtime that Native Images are sensible to:
1. Loading resource bundles for message internationalization
2. Some reflection on internal types for schema inspection
3. Reflection on Java types that your application registers with the schema. This happens for example when GraphQL Java is fetching properties from application types
The first two items are handled via reachability metadata that has been contributed by the Spring team to
https://github.com/oracle/graalvm-reachability-metadata/tree/master/metadata/com.graphql-java/graphql-java[the GraalVM reachability metadata repository].
This metadata is automatically fetched by the native compilation tool when building an application that depends on GraphQL Java.
This doesn't cover our third item in the list, as those types are provided by the application itself and must be discovered by another mean.
[[graalvm.server]]
== Native Server applications support
In typical Spring for GraphQL applications, Java types tied to the GraphQL schema are exposed in `@Controller` method signatures
as parameters or return types. During the {spring-framework-ref-docs}/core.html#core.aot[Ahead Of Time processing phase] of the build,
Spring or GraphQL will use its `o.s.g.data.method.annotation.support.SchemaMappingBeanFactoryInitializationAotProcessor` to discover
the relevant types and register reachability metadata accordingly.
This is all done automatically for you if you are building a Spring Boot application with GraalVM support.
If your application is "manually" registering data fetchers, some types are not discoverable as a result.
You should then register them with Spring Framework's `@RegisterReflectionForBinding`:
include::code:GraphQlConfiguration[]
<1> This application declares a `RuntimeWiringConfigurer` that "manually" adds a `DataFetcher`
<2> Through this `DataFetcher`, the `BookRepository` will expose a `Book` type
<3> `@RegisterReflectionForBinding` will register the relevant hints for the `Book` type and all types exposed as fields
[[graalvm.client]]
== Client support
The `GraphQlClient` is not necessarily present as a bean in the application context and it does not expose the Java types used in the schema in method signatures.
The `AotProcessor` strategy described in the section above cannot be used as a result.
For client support, Spring for GraphQL embeds the {github-main-branch}/spring-graphql/src/main/resources/META-INF/native-image/org.springframework.graphql/spring-graphql[relevant reachability metadata for the client infrastructure].
When it comes to Java types used by the application, applications should use a similar strategy as "manual" data fetchers using `@RegisterReflectionForBinding`:
include::code:ProjectService[]
<1> In a Native image, we need to ensure that reflection can be performed on `Project` at runtime
<2> `@RegisterReflectionForBinding` will register the relevant hints for the `Project` type and all types exposed as fields

View File

@@ -1672,6 +1672,7 @@ include::includes/client.adoc[leveloffset=+1]
include::includes/testing.adoc[leveloffset=+1]
include::includes/graalvm-native.adoc[leveloffset=+1]

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.graphql.docs.graalvm.client;
import java.util.List;
public record Project(String slug, String name, List<Releases> releases) {
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.graphql.docs.graalvm.client;
import reactor.core.publisher.Mono;
import org.springframework.aot.hint.annotation.RegisterReflectionForBinding;
import org.springframework.graphql.client.GraphQlClient;
import org.springframework.stereotype.Component;
@Component
@RegisterReflectionForBinding(Project.class) // <2>
public class ProjectService {
private final GraphQlClient graphQlClient;
public ProjectService(GraphQlClient graphQlClient) {
this.graphQlClient = graphQlClient;
}
public Mono<Project> project(String projectSlug) {
String document = """
query projectWithReleases($projectSlug: ID!) {
project(slug: $projectSlug) {
name
releases {
version
}
}
}
""";
return this.graphQlClient.document(document)
.variable("projectSlug", projectSlug)
.retrieve("project")
.toEntity(Project.class); // <1>
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.graphql.docs.graalvm.client;
public record Releases(String version) {
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.graphql.docs.graalvm.server;
public class Book {
Long id;
String name;
Long authorId;
public Book() {
}
public Book(Long id, String name, Long authorId) {
this.id = id;
this.name = name;
this.authorId = authorId;
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.graphql.docs.graalvm.server;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.CrudRepository;
import org.springframework.graphql.data.GraphQlRepository;
@GraphQlRepository
public interface BookRepository extends CrudRepository<Book, Long>, QuerydslPredicateExecutor<Book> {
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.graphql.docs.graalvm.server;
import graphql.schema.DataFetcher;
import org.springframework.aot.hint.annotation.RegisterReflectionForBinding;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.data.query.QuerydslDataFetcher;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
@Configuration
@RegisterReflectionForBinding(Book.class) // <3>
public class GraphQlConfiguration {
@Bean
RuntimeWiringConfigurer customWiringConfigurer(BookRepository bookRepository) { // <1>
DataFetcher dataFetcher = QuerydslDataFetcher.builder(bookRepository).single();
return wiringBuilder -> wiringBuilder
.type("Query", builder -> builder.dataFetcher("book", dataFetcher)); // <2>
}
}