diff --git a/build.gradle b/build.gradle index d9a3a0e5..d0e15d06 100644 --- a/build.gradle +++ b/build.gradle @@ -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" diff --git a/spring-graphql-docs/build.gradle b/spring-graphql-docs/build.gradle index 50f6c156..17082e98 100644 --- a/spring-graphql-docs/build.gradle +++ b/spring-graphql-docs/build.gradle @@ -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" diff --git a/spring-graphql-docs/src/docs/asciidoc/attributes.adoc b/spring-graphql-docs/src/docs/asciidoc/attributes.adoc index 767690af..81e2a8c2 100644 --- a/spring-graphql-docs/src/docs/asciidoc/attributes.adoc +++ b/spring-graphql-docs/src/docs/asciidoc/attributes.adoc @@ -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 \ No newline at end of file diff --git a/spring-graphql-docs/src/docs/asciidoc/includes/graalvm-native.adoc b/spring-graphql-docs/src/docs/asciidoc/includes/graalvm-native.adoc new file mode 100644 index 00000000..794fac79 --- /dev/null +++ b/spring-graphql-docs/src/docs/asciidoc/includes/graalvm-native.adoc @@ -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 + diff --git a/spring-graphql-docs/src/docs/asciidoc/index.adoc b/spring-graphql-docs/src/docs/asciidoc/index.adoc index 58b4d11b..835c88fc 100644 --- a/spring-graphql-docs/src/docs/asciidoc/index.adoc +++ b/spring-graphql-docs/src/docs/asciidoc/index.adoc @@ -1672,6 +1672,7 @@ include::includes/client.adoc[leveloffset=+1] include::includes/testing.adoc[leveloffset=+1] +include::includes/graalvm-native.adoc[leveloffset=+1] diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/graalvm/client/Project.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/graalvm/client/Project.java new file mode 100644 index 00000000..cb04fb5e --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/graalvm/client/Project.java @@ -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) { +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/graalvm/client/ProjectService.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/graalvm/client/ProjectService.java new file mode 100644 index 00000000..c0a69baa --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/graalvm/client/ProjectService.java @@ -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(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> + } +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/graalvm/client/Releases.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/graalvm/client/Releases.java new file mode 100644 index 00000000..363ea959 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/graalvm/client/Releases.java @@ -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) { +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/graalvm/server/Book.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/graalvm/server/Book.java new file mode 100644 index 00000000..4225adfc --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/graalvm/server/Book.java @@ -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; + } + +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/graalvm/server/BookRepository.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/graalvm/server/BookRepository.java new file mode 100644 index 00000000..19da4959 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/graalvm/server/BookRepository.java @@ -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, QuerydslPredicateExecutor { +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/graalvm/server/GraphQlConfiguration.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/graalvm/server/GraphQlConfiguration.java new file mode 100644 index 00000000..4a8168c8 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/graalvm/server/GraphQlConfiguration.java @@ -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> + } + +}