From 1d98f9b45fc2f9fc55667735273d26903942953c Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 16 Apr 2024 17:16:44 +0200 Subject: [PATCH] Document schema Namespacing with annotated controllers Closes gh-863 --- spring-graphql-docs/antora.yml | 1 + spring-graphql-docs/build.gradle | 1 + .../modules/ROOT/pages/controllers.adoc | 64 +++++++++++++++++++ .../docs/controllers/namespacing/Album.java | 20 ++++++ .../docs/controllers/namespacing/Artist.java | 20 ++++++ .../namespacing/MusicController.java | 51 +++++++++++++++ .../namespacing/NamespaceConfiguration.java | 39 +++++++++++ .../resources/controllers/namespaces.graphqls | 28 ++++++++ 8 files changed, 224 insertions(+) create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/controllers/namespacing/Album.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/controllers/namespacing/Artist.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/controllers/namespacing/MusicController.java create mode 100644 spring-graphql-docs/src/main/java/org/springframework/graphql/docs/controllers/namespacing/NamespaceConfiguration.java create mode 100644 spring-graphql-docs/src/main/resources/controllers/namespaces.graphqls diff --git a/spring-graphql-docs/antora.yml b/spring-graphql-docs/antora.yml index 5663081e..b5049c4a 100644 --- a/spring-graphql-docs/antora.yml +++ b/spring-graphql-docs/antora.yml @@ -17,6 +17,7 @@ asciidoc: chomp: 'all' docs-site: https://docs.spring.io include-java: 'example$docs-src/main/java/org/springframework/graphql/docs' + include-resources: 'example$docs-src/main/resources' github-tag: main github-repo: spring-projects/spring-graphql github-raw: https://raw.githubusercontent.com/{github-repo}/{github-tag} diff --git a/spring-graphql-docs/build.gradle b/spring-graphql-docs/build.gradle index a0f7c147..27ecd6de 100644 --- a/spring-graphql-docs/build.gradle +++ b/spring-graphql-docs/build.gradle @@ -25,6 +25,7 @@ dependencies { api 'org.springframework:spring-messaging' api 'org.springframework.data:spring-data-commons' api 'com.querydsl:querydsl-core' + api "org.springframework.boot:spring-boot-starter-graphql:${springBootVersion}" } jar { diff --git a/spring-graphql-docs/modules/ROOT/pages/controllers.adoc b/spring-graphql-docs/modules/ROOT/pages/controllers.adoc index 07dd9804..92df154b 100644 --- a/spring-graphql-docs/modules/ROOT/pages/controllers.adoc +++ b/spring-graphql-docs/modules/ROOT/pages/controllers.adoc @@ -754,3 +754,67 @@ Supported return types are listed below: | For asynchronous resolution where `` is one of the supported, synchronous, return types. |=== + + + +[[controllers.namespacing]] +== Namespacing + +At the schema level, query and mutation operations are defined directly under the `Query` and `Mutation` types. +Rich GraphQL APIs can define dozens of operation sunder those types, making it harder to explore the API and separate concerns. +You can choose to https://www.apollographql.com/docs/technotes/TN0012-namespacing-by-separation-of-concern/[define Namespaces in your GraphQL schema]. +While there are some caveats with this approach, you can implement this pattern with Spring for GraphQL annotated controllers. + +With namespacing, your GraphQL schema can, for example, nest query operations under top-level types, instead of listing them directly under `Query`. +Here, we will define `MusicQueries` and `UserQueries` types and make them available under `Query`: + +[source,json,subs="verbatim,quotes"] +---- +include::ROOT:{include-resources}/controllers/namespaces.graphqls[] +---- + +A GraphQL client would use the `album` query like this: + +[source,graphql,subs="verbatim,quotes"] +---- +{ + music { + album(id: 42) { + id + title + } + } +} +---- + +And get the following response: + +[source,json,subs="verbatim,quotes"] +---- +{ + "data": { + "music": { + "album": { + "id": "42", + "title": "Spring for GraphQL" + } + } + } +} +---- + + +This can be implemented in a `@Controller` with the following pattern: + +include-code::MusicController[] +<1> Annotate the controller with `@SchemaMapping` and a `typeName` attribute, to avoid repeating it on methods +<2> Define a `@QueryMapping` for the "music" namespace +<3> The "music" query returns an "empty" record, but could also return an empty map +<4> Queries are now declared as fields under the "MusicQueries" type + +Instead of declaring wrapping types ("MusicQueries", "UserQueries") explicitly in controllers, +you can choose to configure them with the runtime wiring using a `GraphQlSourceBuilderCustomizer` with Spring Boot: + +include-code::NamespaceConfiguration[] +<1> List all the wrapper types for the "Query" type +<2> Manually declare data fetchers for each of them, returning an empty Map diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/controllers/namespacing/Album.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/controllers/namespacing/Album.java new file mode 100644 index 00000000..70adb544 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/controllers/namespacing/Album.java @@ -0,0 +1,20 @@ +/* + * Copyright 2020-2024 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.controllers.namespacing; + +public record Album(String id, String title) { +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/controllers/namespacing/Artist.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/controllers/namespacing/Artist.java new file mode 100644 index 00000000..60c0434e --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/controllers/namespacing/Artist.java @@ -0,0 +1,20 @@ +/* + * Copyright 2020-2024 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.controllers.namespacing; + +public record Artist(String id, String name) { +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/controllers/namespacing/MusicController.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/controllers/namespacing/MusicController.java new file mode 100644 index 00000000..be92ed8f --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/controllers/namespacing/MusicController.java @@ -0,0 +1,51 @@ +/* + * Copyright 2020-2024 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.controllers.namespacing; + +import java.util.List; + +import org.springframework.graphql.data.method.annotation.Argument; +import org.springframework.graphql.data.method.annotation.QueryMapping; +import org.springframework.graphql.data.method.annotation.SchemaMapping; +import org.springframework.stereotype.Controller; + +@Controller +@SchemaMapping(typeName="MusicQueries") // <1> +public class MusicController { + + @QueryMapping // <2> + public MusicQueries music() { + return new MusicQueries(); + } + + // <3> + public record MusicQueries() { + + } + + @SchemaMapping // <4> + public Album album(@Argument String id) { + return new Album(id, "Spring GraphQL"); + } + + @SchemaMapping + public List searchForArtist(@Argument String name) { + return List.of(new Artist("100", "the Spring team")); + } + + +} diff --git a/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/controllers/namespacing/NamespaceConfiguration.java b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/controllers/namespacing/NamespaceConfiguration.java new file mode 100644 index 00000000..ab400e94 --- /dev/null +++ b/spring-graphql-docs/src/main/java/org/springframework/graphql/docs/controllers/namespacing/NamespaceConfiguration.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020-2024 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.controllers.namespacing; + +import java.util.Collections; +import java.util.List; + +import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +@Configuration +public class NamespaceConfiguration { + + @Bean + public GraphQlSourceBuilderCustomizer customizer() { + List queryWrappers = List.of("music", "users"); // <1> + return sourceBuilder -> sourceBuilder.configureRuntimeWiring(wiringBuilder -> { + queryWrappers.forEach(field -> wiringBuilder.type("Query", + builder -> builder.dataFetcher(field, env -> Collections.emptyMap()))); // <2> + }); + } + +} diff --git a/spring-graphql-docs/src/main/resources/controllers/namespaces.graphqls b/spring-graphql-docs/src/main/resources/controllers/namespaces.graphqls new file mode 100644 index 00000000..0b4dcd2a --- /dev/null +++ b/spring-graphql-docs/src/main/resources/controllers/namespaces.graphqls @@ -0,0 +1,28 @@ +type Query { + music: MusicQueries + users: UserQueries +} + +type MusicQueries { + album(id: ID!): Album + searchForArtist(name: String!): [Artist] +} + +type Album { + id: ID! + title: String! +} + +type Artist { + id: ID! + name: String! +} + +type UserQueries { + user(login: String): User +} + +type User { + id: ID! + login: String! +}