Document Spring GraphQL support

This commit documents all the features added in the previous commits:
from the main infrastructure support, to testing and metrics.

See gh-29140
pull/29177/head
Brian Clozel 3 years ago
parent c522a8007b
commit 22706057f0

@ -137,6 +137,8 @@ dependencies {
implementation("org.springframework.data:spring-data-neo4j") implementation("org.springframework.data:spring-data-neo4j")
implementation("org.springframework.data:spring-data-redis") implementation("org.springframework.data:spring-data-redis")
implementation("org.springframework.data:spring-data-r2dbc") implementation("org.springframework.data:spring-data-r2dbc")
implementation("org.springframework.graphql:spring-graphql")
implementation("org.springframework.graphql:spring-graphql-test")
implementation("org.springframework.kafka:spring-kafka") implementation("org.springframework.kafka:spring-kafka")
implementation("org.springframework.kafka:spring-kafka-test") implementation("org.springframework.kafka:spring-kafka-test")
implementation("org.springframework.restdocs:spring-restdocs-mockmvc") { implementation("org.springframework.restdocs:spring-restdocs-mockmvc") {
@ -277,8 +279,9 @@ tasks.withType(org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask) {
"spring-data-r2dbc-version": versionConstraints["org.springframework.data:spring-data-r2dbc"], "spring-data-r2dbc-version": versionConstraints["org.springframework.data:spring-data-r2dbc"],
"spring-data-rest-version": versionConstraints["org.springframework.data:spring-data-rest-core"], "spring-data-rest-version": versionConstraints["org.springframework.data:spring-data-rest-core"],
"spring-framework-version": versionConstraints["org.springframework:spring-core"], "spring-framework-version": versionConstraints["org.springframework:spring-core"],
"spring-kafka-version": versionConstraints["org.springframework.kafka:spring-kafka"], "spring-graphql-version": versionConstraints["org.springframework.graphql:spring-graphql"],
"spring-integration-version": versionConstraints["org.springframework.integration:spring-integration-core"], "spring-integration-version": versionConstraints["org.springframework.integration:spring-integration-core"],
"spring-kafka-version": versionConstraints["org.springframework.kafka:spring-kafka"],
"spring-security-version": securityVersion, "spring-security-version": securityVersion,
"spring-webservices-version": versionConstraints["org.springframework.ws:spring-ws-core"] "spring-webservices-version": versionConstraints["org.springframework.ws:spring-ws-core"]
} }
@ -321,6 +324,9 @@ syncDocumentationSourceForAsciidoctor {
from("src/main/groovy") { from("src/main/groovy") {
into "main/groovy" into "main/groovy"
} }
from("src/main/resources") {
into "main/resources"
}
} }
syncDocumentationSourceForAsciidoctorMultipage { syncDocumentationSourceForAsciidoctorMultipage {
@ -342,6 +348,9 @@ syncDocumentationSourceForAsciidoctorMultipage {
from("src/main/groovy") { from("src/main/groovy") {
into "main/groovy" into "main/groovy"
} }
from("src/main/resources") {
into "main/resources"
}
} }
syncDocumentationSourceForAsciidoctorPdf { syncDocumentationSourceForAsciidoctorPdf {
@ -363,6 +372,9 @@ syncDocumentationSourceForAsciidoctorPdf {
from("src/main/groovy") { from("src/main/groovy") {
into "main/groovy" into "main/groovy"
} }
from("src/main/resources") {
into "main/resources"
}
} }
task zip(type: Zip) { task zip(type: Zip) {

@ -873,6 +873,58 @@ A `CacheMetricsRegistrar` bean is made available to make that process easier.
[[actuator.metrics.supported.spring-graphql]]
==== Spring GraphQL Metrics
Auto-configuration enables the instrumentation of GraphQL queries, for any supported transport.
Spring Boot records a `graphql.request` timer with:
[cols="1,2,2"]
|===
|Tag | Description| Sample values
|outcome
|Request outcome
|"SUCCESS", "ERROR"
|===
A single GraphQL query can involve many `DataFetcher` calls, so there is a dedicated `graphql.datafetcher` timer:
[cols="1,2,2"]
|===
|Tag | Description| Sample values
|path
|data fetcher path
|"Query.project"
|outcome
|data fetching outcome
|"SUCCESS", "ERROR"
|===
The `graphql.request.datafetch.count` https://micrometer.io/docs/concepts#_distribution_summaries[distribution summary] counts the number of non-trivial `DataFetcher` calls made per request.
This metric is useful for detecting "N+1" data fetching issues and consider batch loading; it provides the `"TOTAL"` number of data fetcher calls made over the `"COUNT"` of recorded requests, as well as the `"MAX"` calls made for a single request over the considered period.
More options are available for <<application-properties#application-properties.actuator.management.metrics.distribution.maximum-expected-value, configuring distributions with application properties>>.
A single response can contain many GraphQL errors, counted by the `graphql.error` counter:
[cols="1,2,2"]
|===
|Tag | Description| Sample values
|errorType
|error type
|"DataFetchingException"
|errorPath
|error JSON Path
|"$.project"
|===
[[actuator.metrics.supported.jdbc]] [[actuator.metrics.supported.jdbc]]
==== DataSource Metrics ==== DataSource Metrics
Auto-configuration enables the instrumentation of all available `DataSource` objects with metrics prefixed with `jdbc.connections`. Auto-configuration enables the instrumentation of all available `DataSource` objects with metrics prefixed with `jdbc.connections`.

@ -21,6 +21,7 @@
:github-wiki: https://github.com/{github-repo}/wiki :github-wiki: https://github.com/{github-repo}/wiki
:docs-java: ../../main/java/org/springframework/boot/docs :docs-java: ../../main/java/org/springframework/boot/docs
:docs-groovy: ../../main/groovy/org/springframework/boot/docs :docs-groovy: ../../main/groovy/org/springframework/boot/docs
:docs-resources: ../../main/resources
:spring-boot-code: https://github.com/{github-repo}/tree/{github-tag} :spring-boot-code: https://github.com/{github-repo}/tree/{github-tag}
:spring-boot-api: https://docs.spring.io/spring-boot/docs/{spring-boot-version}/api :spring-boot-api: https://docs.spring.io/spring-boot/docs/{spring-boot-version}/api
:spring-boot-docs: https://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference :spring-boot-docs: https://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference
@ -81,6 +82,9 @@
:spring-framework: https://spring.io/projects/spring-framework :spring-framework: https://spring.io/projects/spring-framework
:spring-framework-api: https://docs.spring.io/spring-framework/docs/{spring-framework-version}/javadoc-api/org/springframework :spring-framework-api: https://docs.spring.io/spring-framework/docs/{spring-framework-version}/javadoc-api/org/springframework
:spring-framework-docs: https://docs.spring.io/spring-framework/docs/{spring-framework-version}/reference/html :spring-framework-docs: https://docs.spring.io/spring-framework/docs/{spring-framework-version}/reference/html
:spring-graphql: https://spring.io/projects/spring-graphql
:spring-graphql-api: https://docs.spring.io/spring-graphql/docs/{spring-graphql-version}/api/
:spring-graphql-docs: https://docs.spring.io/spring-graphql/docs/{spring-graphql-version}/reference/html/
:spring-integration: https://spring.io/projects/spring-integration :spring-integration: https://spring.io/projects/spring-integration
:spring-integration-docs: https://docs.spring.io/spring-integration/docs/{spring-integration-version}/reference/html/ :spring-integration-docs: https://docs.spring.io/spring-integration/docs/{spring-integration-version}/reference/html/
:spring-kafka-docs: https://docs.spring.io/spring-kafka/docs/{spring-kafka-version}/reference/html/ :spring-kafka-docs: https://docs.spring.io/spring-kafka/docs/{spring-kafka-version}/reference/html/

@ -448,6 +448,62 @@ TIP: Sometimes writing Spring WebFlux tests is not enough; Spring Boot can help
[[features.testing.spring-boot-applications.spring-graphql-tests]]
==== Auto-configured Spring GraphQL Tests
Spring GraphQL offers a dedicated testing support module; you'll need to add it to your project:
.Maven
[source,xml,indent=0,subs="verbatim"]
----
<dependencies>
<dependency>
<groupId>org.springframework.graphql</groupId>
<artifactId>spring-graphql-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Unless already present in the compile scope -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
----
.Gradle
[source,gradle,indent=0,subs="verbatim"]
----
dependencies {
testImplementation("org.springframework.graphql:spring-graphql-test")
// Unless already present in the implementation configuration
testImplementation("org.springframework:spring-webflux")
}
----
This testing module ships the {spring-graphql-docs}/testing.html#testing-webgraphqltester[WebGraphQlTester].
The tester is heavily used in test, so be sure to become familiar with using it.
Spring Boot helps you to test your {spring-graphql-docs}#controllers[Spring GraphQL Controllers] with the `@GraphQlTest` annotation.
`@GraphQlTest` auto-configures the Spring GraphQL infrastructure, without any transport nor server being involved.
This limits scanned beans to `@Controller`, `RuntimeWiringConfigurer`, `JsonComponent`, `Converter` and `GenericConverter`.
Regular `@Component` and `@ConfigurationProperties` beans are not scanned when the `@GraphQlTest` annotation is used.
`@EnableConfigurationProperties` can be used to include `@ConfigurationProperties` beans.
TIP: A list of the auto-configurations that are enabled by `@GraphQlTest` can be <<test-auto-configuration#test-auto-configuration,found in the appendix>>.
TIP: If you need to register extra components, such as Jackson `Module`, you can import additional configuration classes using `@Import` on your test.
Often, `@GraphQlTest` is limited to a set of controllers and used in combination with the `@MockBean` annotation to provide mock implementations for required collaborators.
[source,java,indent=0,subs="verbatim"]
----
include::{docs-java}/features/testing/springbootapplications/springgraphqltests/GreetingControllerTests.java[]
----
TIP: You can also auto-configure `WebGraphQlTester` in a non-`@GraphQlTest` (such as `@SpringBootTest`) by annotating it with `@AutoConfigureWebGraphQlTester`.
[[features.testing.spring-boot-applications.autoconfigured-spring-data-cassandra]] [[features.testing.spring-boot-applications.autoconfigured-spring-data-cassandra]]
==== Auto-configured Data Cassandra Tests ==== Auto-configured Data Cassandra Tests
You can use `@DataCassandraTest` to test Cassandra applications. You can use `@DataCassandraTest` to test Cassandra applications.

@ -16,7 +16,7 @@ The reference documentation consists of the following sections:
<<upgrading#upgrading,Upgrading Spring Boot Applications>> :: Upgrading from 1.x, Upgrading to a new feature release, and Upgrading the Spring Boot CLI. <<upgrading#upgrading,Upgrading Spring Boot Applications>> :: Upgrading from 1.x, Upgrading to a new feature release, and Upgrading the Spring Boot CLI.
<<using#using,Using Spring Boot>> :: Build Systems, Structuring Your Code, Configuration, Spring Beans and Dependency Injection, DevTools, and more. <<using#using,Using Spring Boot>> :: Build Systems, Structuring Your Code, Configuration, Spring Beans and Dependency Injection, DevTools, and more.
<<features#features,Core Features>> :: Profiles, Logging, Security, Caching, Spring Integration, Testing, and more. <<features#features,Core Features>> :: Profiles, Logging, Security, Caching, Spring Integration, Testing, and more.
<<web#web,Web>> :: Servlet Web, Reactive Web, Embedded Container Support, Graceful Shutdown, and more. <<web#web,Web>> :: Servlet Web, Reactive Web, GraphQL, Embedded Container Support, Graceful Shutdown, and more.
<<data#data,Data>> :: SQL and NOSQL data access. <<data#data,Data>> :: SQL and NOSQL data access.
<<io#io,IO>> :: Caching, Quartz Scheduler, REST clients, Sending email, Spring Web Services, and more. <<io#io,IO>> :: Caching, Quartz Scheduler, REST clients, Sending email, Spring Web Services, and more.
<<messaging#messaging,Messaging>> :: JMS, AMQP, RSocket, WebSocket, and Spring Integration. <<messaging#messaging,Messaging>> :: JMS, AMQP, RSocket, WebSocket, and Spring Integration.

@ -19,6 +19,8 @@ include::web/spring-security.adoc[]
include::web/spring-session.adoc[] include::web/spring-session.adoc[]
include::web/spring-graphql.adoc[]
include::web/spring-hateoas.adoc[] include::web/spring-hateoas.adoc[]
include::web/whats-next.adoc[] include::web/whats-next.adoc[]

@ -0,0 +1,135 @@
[[web.graphql]]
== Spring GraphQL
If you want to build GraphQL applications, you can take advantage of Spring Boot's auto-configuration for {spring-graphql}[Spring GraphQL].
The Spring GraphQL project is based on https://github.com/graphql-java/graphql-java[GraphQL Java].
You'll need the `spring-boot-starter-graphql` starter at a minimum.
Because GraphQL is transport-agnostic, you'll also need to have one or more additional starters in your application to expose your GraphQL API over the web:
[cols="1,1,1"]
|===
| Starter | Transport | Implementation
| `spring-boot-starter-web`
| HTTP
| Spring MVC
| `spring-boot-starter-websocket`
| WebSocket
| WebSocket for Servlet apps
| `spring-boot-starter-webflux`
| HTTP, WebSocket
| Spring WebFlux
|===
[[web.graphql.schema]]
=== GraphQL Schema
A Spring GraphQL application requires a defined schema at startup.
By default, you can write ".graphqls" or ".gqls" schema files under `src/main/resources/graphql/**` and Spring Boot will pick them up automatically.
You can customize the locations with configprop:spring.graphql.schema.locations[] and the file extensions with configprop:spring.graphql.schema.file-extensions[].
In the following sections, we'll consider this sample GraphQL schema, defining two types and two queries:
[source,json,indent=0,subs="verbatim,quotes"]
----
include::{docs-resources}/graphql/schema.graphqls[]
----
[[web.graphql.runtimewiring]]
=== GraphQL RuntimeWiring
The GraphQL Java `RuntimeWiring.Builder` can be used to register custom scalar types, directives, type resolvers, `DataFetcher`s, and more.
You can declare `RuntimeWiringConfigurer` beans in your Spring config to get access to the `RuntimeWiring.Builder`.
Spring Boot detects such beans and adds them to the {spring-graphql-docs}#execution-graphqlsource[GraphQlSource builder].
Typically, however, applications will not implement `DataFetcher` directly and will instead create {spring-graphql-docs}#controllers[annotated controllers].
Spring Boot will automatically register `@Controller` classes with annotated handler methods and registers those as `DataFetcher`s.
Here's a sample implementation for our greeting query with a `@Controller` class:
[source,java,indent=0,subs="verbatim"]
----
include::{docs-java}/web/graphql/GreetingController.java[]
----
[[web.graphql.data-query]]
=== Querydsl and QueryByExample Repositories support
Spring Data offers support for both Querydsl and QueryByExample repositories.
Spring GraphQL can {spring-graphql-docs}#data[configure Querydsl and QueryByExample repositories as `DataFetcher`].
Spring Data repositories annotated with `@GraphQlRepository` and extending one of:
* `QuerydslPredicateExecutor`
* `ReactiveQuerydslPredicateExecutor`
* `QueryByExampleExecutor`
* `ReactiveQueryByExampleExecutor`
are detected by Spring Boot and considered as candidates for `DataFetcher` for matching top-level queries.
[[web.graphql.web-endpoints]]
=== Web Endpoints
The GraphQL HTTP endpoint is at HTTP POST "/graphql" by default. The path can be customized with configprop:spring.graphql.path[].
The GraphQL WebSocket endpoint is off by default. To enable it:
* For a Servlet application, add the WebSocket starter `spring-boot-starter-websocket`
* For a WebFlux application, no additional dependency is required
* For both, the configprop:spring.graphql.websocket.path[] application property must be set
Spring GraphQL provides a {spring-graphql-docs}#web-interception[Web Interception] model.
This is quite useful for retrieving information from an HTTP request header and set it in the GraphQL context or fetching information from the same context and writing it to a response header.
With Spring Boot, you can declare a `WebInterceptor` bean to have it registered with the web transport.
[[web.graphql.cors]]
=== CORS
{spring-framework-docs}/web.html#mvc-cors[Spring MVC] and {spring-framework-docs}/web-reactive.html#webflux-cors[Spring WebFlux] support CORS (Cross-Origin Resource Sharing) requests.
CORS is a critical part of the web config for GraphQL applications that are accessed from browsers using different domains.
Spring Boot supports many configuration properties under the `spring.graphql.cors.*` namespace; here's a short configuration sample:
[source,yaml,indent=0,subs="verbatim",configblocks]
----
spring:
graphql:
cors:
allowed-origins: "https://example.org"
allowed-methods: GET,POST
max-age: 1800s
----
[[web.graphql.exception-handling]]
=== Exceptions Handling
Spring GraphQL enables applications to register one or more Spring `DataFetcherExceptionResolver` components that are invoked sequentially.
The Exception must be resolved to a list of `graphql.GraphQLError` objects, see {spring-graphql-docs}#execution-exceptions[Spring GraphQL exception handling documentation].
Spring Boot will automatically detect `DataFetcherExceptionResolver` beans and register them with the `GraphQlSource.Builder`.
[[web.graphql.graphiql]]
=== GraphiQL and Schema printer
Spring GraphQL offers infrastructure for helping developers when consuming or developing a GraphQL API.
Spring GraphQL ships with a default https://github.com/graphql/graphiql[GraphiQL] page that is exposed at "/graphiql" by default.
This page is disabled by default and can be turned on with the configprop:spring.graphql.graphiql.enabled[] property.
Many applications exposing such a page will prefer a custom build.
A default implementation is very useful during development, this is why it is exposed automatically with <<using#using.devtools,`spring-boot-devtools`>> during development.
You can also choose to expose the GraphQL schema in text format at `/graphql/schema` when the configprop:spring.graphql.schema.printer.enabled[] property is enabled.

@ -0,0 +1,44 @@
/*
* Copyright 2012-2021 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.boot.docs.features.testing.springbootapplications.springgraphqltests;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.docs.web.graphql.GreetingController;
import org.springframework.boot.test.autoconfigure.graphql.GraphQlTest;
import org.springframework.graphql.test.tester.GraphQlTester;
@GraphQlTest(GreetingController.class)
class GreetingControllerTests {
@Autowired
private GraphQlTester graphQlTester;
@Test
void shouldGreetWithSpecificName() {
this.graphQlTester.query("{ greeting(name: \"Alice\") } ").execute().path("greeting").entity(String.class)
.isEqualTo("Hello, Alice!");
}
@Test
void shouldGreetWithDefaultName() {
this.graphQlTester.query("{ greeting } ").execute().path("greeting").entity(String.class)
.isEqualTo("Hello, Spring!");
}
}

@ -0,0 +1,31 @@
/*
* Copyright 2002-2021 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.boot.docs.web.graphql;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;
@Controller
public class GreetingController {
@QueryMapping
public String greeting(@Argument String name) {
return "Hello, " + name + "!";
}
}

@ -0,0 +1,29 @@
type Query {
greeting(name: String! = "Spring"): String!
project(slug: ID!): Project
}
""" A Project in the Spring portfolio """
type Project {
""" Unique string id used in URLs """
slug: ID!
""" Project name """
name: String!
""" URL of the git repository """
repositoryUrl: String!
""" Current support status """
status: ProjectStatus!
}
enum ProjectStatus {
""" Actively supported by the Spring team """
ACTIVE
""" Supported by the community """
COMMUNITY
""" Prototype, not officially supported yet """
INCUBATING
""" Project being retired, in maintenance mode """
ATTIC
""" End-Of-Lifed """
EOL
}
Loading…
Cancel
Save