├── .circleci
└── config.yml
├── .editorconfig
├── .gitattributes
├── .github
└── workflows
│ └── continuous-integration.yaml
├── .gitignore
├── CODEOWNERS
├── LICENSE
├── README.md
├── docker-compose.yaml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── products-subgraph
├── Dockerfile
├── README.md
├── build.gradle.kts
└── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── products
│ │ │ ├── ProductsApplication.java
│ │ │ ├── ProductsController.java
│ │ │ └── model
│ │ │ ├── Product.java
│ │ │ └── ProductSource.java
│ └── resources
│ │ ├── application.yml
│ │ └── graphql
│ │ └── schema.graphqls
│ └── test
│ └── java
│ └── com
│ └── example
│ └── products
│ └── ProductsApplicationTest.java
├── renovate.json
├── reviews-subgraph
├── Dockerfile
├── README.md
├── build.gradle.kts
└── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── reviews
│ │ │ ├── ReviewsApplication.java
│ │ │ ├── ReviewsController.java
│ │ │ └── model
│ │ │ ├── Product.java
│ │ │ ├── Review.java
│ │ │ └── ReviewSource.java
│ └── resources
│ │ ├── application.yml
│ │ └── graphql
│ │ └── schema.graphqls
│ └── test
│ └── java
│ └── com
│ └── example
│ └── reviews
│ └── ReviewsApplicationTest.java
├── router.yaml
├── settings.gradle.kts
└── supergraph.yaml
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | orbs:
4 | secops: apollo/circleci-secops-orb@2.0.7
5 |
6 | workflows:
7 | security-scans:
8 | jobs:
9 | - secops/gitleaks:
10 | context:
11 | - platform-docker-ro
12 | - github-orb
13 | - secops-oidc
14 | git-base-revision: <<#pipeline.git.base_revision>><><>
15 | git-revision: << pipeline.git.revision >>
16 | - secops/semgrep:
17 | context:
18 | - secops-oidc
19 | - github-orb
20 | git-base-revision: <<#pipeline.git.base_revision>><><>
21 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | charset = utf-8
9 | end_of_line = lf
10 | indent_style = space
11 | insert_final_newline = true
12 | trim_trailing_whitespace = true
13 |
14 | # space indentation for JSON and YML
15 | [*.{json,yml}]
16 | indent_size = 2
17 |
18 | # space indentation for Java
19 | [*.java]
20 | indent_size = 2
21 | max_line_length = 200
22 |
23 | [*.kts]
24 | indent_size = 4
25 | max_line_length = 200
26 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | #
2 | # https://help.github.com/articles/dealing-with-line-endings/
3 | #
4 | # Linux start script should use lf
5 | /gradlew text eol=lf
6 |
7 | # These are Windows script files and should use crlf
8 | *.bat text eol=crlf
9 |
10 |
--------------------------------------------------------------------------------
/.github/workflows/continuous-integration.yaml:
--------------------------------------------------------------------------------
1 | name: Continuous Integration
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | build-products:
13 | timeout-minutes: 5
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v3
18 | - name: Validate Gradle wrapper
19 | uses: gradle/wrapper-validation-action@v1
20 | - name: Set up Java 17
21 | uses: actions/setup-java@v3
22 | with:
23 | java-version: 17
24 | distribution: 'zulu'
25 | - name: Set up Gradle cache
26 | uses: gradle/gradle-build-action@v2
27 | - name: Build products subgraph with Gradle
28 | run: ./gradlew :products-subgraph:clean :products-subgraph:build :products-subgraph:bootJar
29 | - name: Upload JAR
30 | uses: actions/upload-artifact@v3
31 | with:
32 | name: products.jar
33 | path: ./products-subgraph/build/libs/products.jar
34 | retention-days: 1
35 |
36 |
37 | build-reviews:
38 | timeout-minutes: 5
39 | runs-on: ubuntu-latest
40 |
41 | steps:
42 | - uses: actions/checkout@v3
43 | - name: Validate Gradle wrapper
44 | uses: gradle/wrapper-validation-action@v1
45 | - name: Set up Java 17
46 | uses: actions/setup-java@v3
47 | with:
48 | java-version: 17
49 | distribution: 'zulu'
50 | - name: Set up Gradle cache
51 | uses: gradle/gradle-build-action@v2
52 | - name: Build reviews subgraph with Gradle
53 | run: ./gradlew :reviews-subgraph:clean :reviews-subgraph:build :reviews-subgraph:bootJar
54 | - name: Upload JAR
55 | uses: actions/upload-artifact@v3
56 | with:
57 | name: reviews.jar
58 | path: ./reviews-subgraph/build/libs/reviews.jar
59 | retention-days: 1
60 |
61 | build-supergraph:
62 | timeout-minutes: 5
63 | runs-on: ubuntu-latest
64 |
65 | steps:
66 | - uses: actions/checkout@v3
67 | - name: Setup rover CLI
68 | run: |
69 | curl -sSL https://rover.apollo.dev/nix/latest | sh -s -- --elv2-license accept
70 | echo "$HOME/.rover/bin" >> ${GITHUB_PATH}
71 | - name: Compose Supergraph
72 | run: APOLLO_ELV2_LICENSE=accept rover supergraph compose --config supergraph.yaml > supergraph.graphql
73 | - name: Upload Supergraph config
74 | uses: actions/upload-artifact@v3
75 | with:
76 | name: supergraph.graphql
77 | path: supergraph.graphql
78 | retention-days: 1
79 |
80 | federation-test:
81 | timeout-minutes: 10
82 | runs-on: ubuntu-latest
83 | needs: [build-products, build-reviews, build-supergraph]
84 |
85 | steps:
86 | - uses: actions/checkout@v3
87 | # we are using separate download actions as otherwise artifacts are placed in folders
88 | - name: Download products JAR
89 | uses: actions/download-artifact@v3
90 | with:
91 | name: products.jar
92 | - name: Download reviews JAR
93 | uses: actions/download-artifact@v3
94 | with:
95 | name: reviews.jar
96 | - name: Download Supergraph config
97 | uses: actions/download-artifact@v3
98 | with:
99 | name: supergraph.graphql
100 | - name: Start up Supergraph
101 | run: docker compose up --build --detach --wait
102 | - name: Federation Tests
103 | run: |
104 | set -x
105 | echo "verify router is up"
106 | curl --verbose http://localhost:8088/health
107 |
108 | echo "sending a test query"
109 | curl --request POST \
110 | --verbose \
111 | --header 'content-type: application/json' \
112 | --url http://localhost:3000/ \
113 | --data '{"query":"query($productId: ID!) {\n product(id: $productId) {\n id\n reviews {\n id\n text\n starRating\n }\n name\n description\n }\n}","variables":{"productId":"5"}}' \
114 | > response.json
115 |
116 | echo "received GraphQL response"
117 | cat response.json
118 |
119 | echo "verifying response"
120 | jq -e '.data.product?.id == "5" and .data.product?.name == "Dragon" and (.data.product?.reviews | length == 2) and (.data.product?.reviews[0]?.text | length > 0)' response.json
121 | - name: Error Logs
122 | if: ${{ failure() }}
123 | run: docker-compose logs
124 | - name: Stop Supergraph
125 | if: ${{ always() }}
126 | run: docker compose down --remove-orphans
127 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # idea
2 | *.iml
3 | .idea/
4 |
5 | # mac
6 | .DS_Store
7 |
8 | # gradle ignore
9 | .gradle
10 | build/
11 | out/
12 |
13 | # ignore files used for composition
14 | *.jar
15 | supergraph.graphql
16 | response.json
17 |
18 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
19 | !gradle-wrapper.jar
20 |
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # This file was automatically generated by the Apollo SecOps team
2 | # Please customize this file as needed prior to merging.
3 |
4 | * @apollographql/fed-core
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Meteor Development Group, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Federation JVM Spring Example
2 |
3 | [Apollo Federation JVM](https://github.com/apollographql/federation-jvm) example implementation using [Spring for GraphQL](https://docs.spring.io/spring-graphql/docs/current/reference/html/).
4 | If you want to discuss the project or just say hi, stop by [the Apollo community forums](https://community.apollographql.com/).
5 |
6 | The repository contains two separate projects:
7 |
8 | 1. `products-subgraph`: A Java GraphQL service providing the federated `Product` type
9 | 2. `reviews-subgraph`: A Java GraphQL service that extends the `Product` type with `reviews`
10 |
11 | See individual projects READMEs for detailed instructions on how to run them.
12 |
13 | Running the demo
14 | ----
15 |
16 | 1. Start `products-subgraph` by running the `ProductsApplication` Spring Boot app from the IDE or by running `./gradlew :products-subgraph:bootRun` from the root project directory
17 | 2. Start `reviews-subgraph` by running the `ReviewsApplication` Spring Boot app from the IDE or `./gradlew :reviews-subgraph:bootRun` from the root project directory
18 | 3. Start Federated Router
19 | 1. Install [rover CLI](https://www.apollographql.com/docs/rover/getting-started)
20 | 2. Start router and compose products schema using [rover dev command](https://www.apollographql.com/docs/rover/commands/dev)
21 |
22 | ```shell
23 | # start up router and compose products schema
24 | rover dev --name products --schema ./products-subgraph/src/main/resources/graphql/schema.graphqls --url http://localhost:8080/graphql
25 | ```
26 |
27 | 3. In **another** shell run `rover dev` to compose reviews schema
28 |
29 | ```shell
30 | rover dev --name reviews --schema ./reviews-subgraph/src/main/resources/graphql/schema.graphqls --url http://localhost:8081/graphql
31 | ```
32 |
33 | 4. Open http://localhost:3000 for the query editor
34 |
35 | Example federated query
36 |
37 | ```graphql
38 | query ExampleQuery {
39 | products {
40 | id
41 | name
42 | description
43 | reviews {
44 | id
45 | text
46 | starRating
47 | }
48 | }
49 | }
50 | ```
51 |
52 | ## Other Federation JVM examples
53 |
54 | * [Netflix DGS Federation Example](https://github.com/Netflix/dgs-federation-example)
55 | * [GraphQL Java Kickstart Federation Example](https://github.com/setchy/graphql-java-kickstart-federation-example)
56 |
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | router:
3 | image: ghcr.io/apollographql/router:v1.32.0
4 | volumes:
5 | - ./router.yaml:/dist/config/router.yaml
6 | - ./supergraph.graphql:/dist/config/supergraph.graphql
7 | ports:
8 | - 3000:3000
9 | - 8088:8088
10 | command: -c config/router.yaml -s config/supergraph.graphql
11 | depends_on:
12 | - products
13 | - reviews
14 | products:
15 | build:
16 | context: .
17 | dockerfile: products-subgraph/Dockerfile
18 | args:
19 | JAR_FILE: ./products.jar
20 | ports:
21 | - 8080:8080
22 | healthcheck:
23 | test: [ "CMD", "curl", "http://products:8080/actuator/health" ]
24 | interval: 5s
25 | timeout: 1s
26 | retries: 10
27 | reviews:
28 | build:
29 | context: .
30 | dockerfile: reviews-subgraph/Dockerfile
31 | args:
32 | JAR_FILE: ./reviews.jar
33 | ports:
34 | - 8081:8081
35 | healthcheck:
36 | test: [ "CMD", "curl", "http://reviews:8081/actuator/health" ]
37 | interval: 5s
38 | timeout: 1s
39 | retries: 10
40 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.caching=true
2 | org.gradle.parallel=true
3 |
4 | federation-jvm.version = 4.2.0
5 | graphql-java.version = 21.1
6 |
7 |
8 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apollographql/federation-jvm-spring-example/aba9c32aceb263e0a48e2e234f0b99ff161f779a/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
4 | networkTimeout=10000
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
87 |
88 | # Use the maximum available, or set MAX_FD != -1 to use that value.
89 | MAX_FD=maximum
90 |
91 | warn () {
92 | echo "$*"
93 | } >&2
94 |
95 | die () {
96 | echo
97 | echo "$*"
98 | echo
99 | exit 1
100 | } >&2
101 |
102 | # OS specific support (must be 'true' or 'false').
103 | cygwin=false
104 | msys=false
105 | darwin=false
106 | nonstop=false
107 | case "$( uname )" in #(
108 | CYGWIN* ) cygwin=true ;; #(
109 | Darwin* ) darwin=true ;; #(
110 | MSYS* | MINGW* ) msys=true ;; #(
111 | NONSTOP* ) nonstop=true ;;
112 | esac
113 |
114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
115 |
116 |
117 | # Determine the Java command to use to start the JVM.
118 | if [ -n "$JAVA_HOME" ] ; then
119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
120 | # IBM's JDK on AIX uses strange locations for the executables
121 | JAVACMD=$JAVA_HOME/jre/sh/java
122 | else
123 | JAVACMD=$JAVA_HOME/bin/java
124 | fi
125 | if [ ! -x "$JAVACMD" ] ; then
126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
127 |
128 | Please set the JAVA_HOME variable in your environment to match the
129 | location of your Java installation."
130 | fi
131 | else
132 | JAVACMD=java
133 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
134 |
135 | Please set the JAVA_HOME variable in your environment to match the
136 | location of your Java installation."
137 | fi
138 |
139 | # Increase the maximum file descriptors if we can.
140 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
141 | case $MAX_FD in #(
142 | max*)
143 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
144 | # shellcheck disable=SC3045
145 | MAX_FD=$( ulimit -H -n ) ||
146 | warn "Could not query maximum file descriptor limit"
147 | esac
148 | case $MAX_FD in #(
149 | '' | soft) :;; #(
150 | *)
151 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
152 | # shellcheck disable=SC3045
153 | ulimit -n "$MAX_FD" ||
154 | warn "Could not set maximum file descriptor limit to $MAX_FD"
155 | esac
156 | fi
157 |
158 | # Collect all arguments for the java command, stacking in reverse order:
159 | # * args from the command line
160 | # * the main class name
161 | # * -classpath
162 | # * -D...appname settings
163 | # * --module-path (only if needed)
164 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
165 |
166 | # For Cygwin or MSYS, switch paths to Windows format before running java
167 | if "$cygwin" || "$msys" ; then
168 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
169 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
170 |
171 | JAVACMD=$( cygpath --unix "$JAVACMD" )
172 |
173 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
174 | for arg do
175 | if
176 | case $arg in #(
177 | -*) false ;; # don't mess with options #(
178 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
179 | [ -e "$t" ] ;; #(
180 | *) false ;;
181 | esac
182 | then
183 | arg=$( cygpath --path --ignore --mixed "$arg" )
184 | fi
185 | # Roll the args list around exactly as many times as the number of
186 | # args, so each arg winds up back in the position where it started, but
187 | # possibly modified.
188 | #
189 | # NB: a `for` loop captures its iteration list before it begins, so
190 | # changing the positional parameters here affects neither the number of
191 | # iterations, nor the values presented in `arg`.
192 | shift # remove old arg
193 | set -- "$@" "$arg" # push replacement arg
194 | done
195 | fi
196 |
197 |
198 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
199 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
200 |
201 | # Collect all arguments for the java command;
202 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
203 | # shell script including quotes and variable substitutions, so put them in
204 | # double quotes to make sure that they get re-expanded; and
205 | # * put everything else in single quotes, so that it's not re-expanded.
206 |
207 | set -- \
208 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
209 | -classpath "$CLASSPATH" \
210 | org.gradle.wrapper.GradleWrapperMain \
211 | "$@"
212 |
213 | # Stop when "xargs" is not available.
214 | if ! command -v xargs >/dev/null 2>&1
215 | then
216 | die "xargs is not available"
217 | fi
218 |
219 | # Use "xargs" to parse quoted args.
220 | #
221 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
222 | #
223 | # In Bash we could simply go:
224 | #
225 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
226 | # set -- "${ARGS[@]}" "$@"
227 | #
228 | # but POSIX shell has neither arrays nor command substitution, so instead we
229 | # post-process each arg (as a line of input to sed) to backslash-escape any
230 | # character that might be a shell metacharacter, then use eval to reverse
231 | # that process (while maintaining the separation between arguments), and wrap
232 | # the whole thing up as a single "set" statement.
233 | #
234 | # This will of course break if any of these variables contains a newline or
235 | # an unmatched quote.
236 | #
237 |
238 | eval "set -- $(
239 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
240 | xargs -n1 |
241 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
242 | tr '\n' ' '
243 | )" '"$@"'
244 |
245 | exec "$JAVACMD" "$@"
246 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/products-subgraph/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM openjdk:17
2 | EXPOSE 8080
3 |
4 | ARG JAR_FILE=build/libs/products.jar
5 | ADD ${JAR_FILE} app.jar
6 | ENTRYPOINT ["java","-jar","/app.jar"]
7 |
--------------------------------------------------------------------------------
/products-subgraph/README.md:
--------------------------------------------------------------------------------
1 | # Products Subgraph
2 |
3 | [Apollo Federation JVM](https://github.com/apollographql/federation-jvm) example subgraph implementation using [Spring GraphQL](https://docs.spring.io/spring-graphql/docs/current/reference/html/) and exposing the federated `Product` type.
4 |
5 | ```graphql
6 | type Query {
7 | product(id: ID!): Product
8 | products: [Product!]!
9 | }
10 |
11 | type Product @key(fields: "id") {
12 | id: ID!
13 | name: String!
14 | description: String
15 | }
16 | ```
17 |
18 | ### Running locally
19 | Build the application by running the following from the project directory:
20 |
21 | ```shell
22 | ./gradlew build
23 | ```
24 |
25 | > NOTE: in order to ensure you use the right version of Gradle we highly recommend to use the provided wrapper scripts
26 |
27 | Start the server:
28 |
29 | * Run `ProductsApplication.java` directly from your IDE
30 | * Alternatively you can also use the Gradle Spring Boot plugin
31 |
32 | ```shell
33 | ./gradlew bootRun
34 | ```
35 |
36 | Once the app has started you can explore the example schema by opening GraphiQL endpoint at http://localhost:8080/graphiql.
37 |
--------------------------------------------------------------------------------
/products-subgraph/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.springframework.boot.gradle.tasks.bundling.BootJar
2 |
3 | plugins {
4 | id("org.springframework.boot") version "3.3.0"
5 | id("io.spring.dependency-management") version "1.1.3"
6 | java
7 | }
8 |
9 | group = "com.example"
10 | version = "0.0.1-SNAPSHOT"
11 |
12 | repositories {
13 | mavenCentral()
14 | }
15 |
16 | val federation_jvm_version: String = project.property("federation-jvm.version").toString()
17 | dependencies {
18 | implementation("com.apollographql.federation:federation-graphql-java-support:$federation_jvm_version")
19 | implementation("org.springframework.boot:spring-boot-starter-actuator")
20 | implementation("org.springframework.boot:spring-boot-starter-graphql")
21 | implementation("org.springframework.boot:spring-boot-starter-web")
22 | testImplementation("org.springframework.boot:spring-boot-starter-test")
23 | testImplementation("org.springframework.boot:spring-boot-starter-webflux")
24 | testImplementation("org.springframework.graphql:spring-graphql-test")
25 | }
26 |
27 | java {
28 | sourceCompatibility = JavaVersion.VERSION_17
29 | targetCompatibility = JavaVersion.VERSION_17
30 | }
31 |
32 | tasks.withType {
33 | useJUnitPlatform()
34 | }
35 |
36 | tasks.named("bootJar") {
37 | archiveFileName.set("products.jar")
38 | }
39 |
--------------------------------------------------------------------------------
/products-subgraph/src/main/java/com/example/products/ProductsApplication.java:
--------------------------------------------------------------------------------
1 | package com.example.products;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer;
6 | import org.springframework.context.annotation.Bean;
7 | import org.springframework.graphql.data.federation.FederationSchemaFactory;
8 |
9 | @SpringBootApplication
10 | public class ProductsApplication {
11 |
12 | public static void main(String[] args) {
13 | SpringApplication.run(ProductsApplication.class, args);
14 | }
15 |
16 | @Bean
17 | public GraphQlSourceBuilderCustomizer customizer(FederationSchemaFactory factory) {
18 | return builder -> builder.schemaFactory(factory::createGraphQLSchema);
19 | }
20 |
21 | @Bean
22 | FederationSchemaFactory federationSchemaFactory() {
23 | return new FederationSchemaFactory();
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/products-subgraph/src/main/java/com/example/products/ProductsController.java:
--------------------------------------------------------------------------------
1 | package com.example.products;
2 |
3 | import java.util.List;
4 |
5 | import com.example.products.model.Product;
6 | import com.example.products.model.ProductSource;
7 |
8 | import org.springframework.graphql.data.method.annotation.Argument;
9 | import org.springframework.graphql.data.method.annotation.QueryMapping;
10 | import org.springframework.stereotype.Controller;
11 |
12 | @Controller
13 | public class ProductsController {
14 |
15 | @QueryMapping
16 | public Product product(@Argument Long id) {
17 | return ProductSource.getProduct(id);
18 | }
19 |
20 | @QueryMapping
21 | public List products() {
22 | return ProductSource.getProducts();
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/products-subgraph/src/main/java/com/example/products/model/Product.java:
--------------------------------------------------------------------------------
1 | package com.example.products.model;
2 |
3 | /**
4 | * type Product @key(fields: "id") {
5 | * id: ID!
6 | * name: String!
7 | * description: String
8 | * }
9 | */
10 | public record Product(Long id, String name, String description) {
11 |
12 | public Product(Long id, String name) {
13 | this(id, name, null);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/products-subgraph/src/main/java/com/example/products/model/ProductSource.java:
--------------------------------------------------------------------------------
1 | package com.example.products.model;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 | import java.util.stream.Collectors;
6 |
7 | public final class ProductSource {
8 |
9 | private static final List productList = List.of(
10 | new Product(1L, "Saturn V", "The Original Super Heavy-Lift Rocket!"),
11 | new Product(2L, "Lunar Module"),
12 | new Product(3L, "Space Shuttle"),
13 | new Product(4L, "Falcon 9", "Reusable Medium-Lift Rocket"),
14 | new Product(5L, "Dragon", "Reusable Medium-Lift Rocket"),
15 | new Product(6L, "Starship", "Super Heavy-Lift Reusable Launch Vehicle")
16 | );
17 |
18 | private static final Map productMap =
19 | productList.stream().collect(Collectors.toMap(Product::id, product -> product));
20 |
21 | public static Product getProduct(Long id) {
22 | return productMap.get(id);
23 | }
24 |
25 | public static List getProducts() {
26 | return productList;
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/products-subgraph/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | graphql:
3 | graphiql:
4 | enabled: true
5 | logging:
6 | level:
7 | org.springframework.graphql: DEBUG
8 |
--------------------------------------------------------------------------------
/products-subgraph/src/main/resources/graphql/schema.graphqls:
--------------------------------------------------------------------------------
1 | type Query {
2 | product(id: ID!): Product
3 | products: [Product!]!
4 | }
5 |
6 | type Product @key(fields: "id") {
7 | id: ID!
8 | name: String!
9 | description: String
10 | }
11 |
--------------------------------------------------------------------------------
/products-subgraph/src/test/java/com/example/products/ProductsApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.example.products;
2 |
3 | import com.example.products.model.Product;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureHttpGraphQlTester;
8 | import org.springframework.boot.test.context.SpringBootTest;
9 | import org.springframework.graphql.test.tester.HttpGraphQlTester;
10 |
11 | @AutoConfigureHttpGraphQlTester
12 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
13 | public class ProductsApplicationTest {
14 |
15 | @Autowired
16 | private HttpGraphQlTester tester;
17 |
18 | @Test
19 | public void verifiesProductQuery() {
20 | String query = """
21 | query ProductById($productId: ID!) {
22 | product(id: $productId) {
23 | id
24 | name
25 | description
26 | }
27 | }
28 | """;
29 |
30 | // new Product("1","Saturn V", "The Original Super Heavy-Lift Rocket!"),
31 | tester.document(query)
32 | .variable("productId", "1")
33 | .execute()
34 | .path("product")
35 | .entity(Product.class)
36 | .isEqualTo(new Product(1L,"Saturn V", "The Original Super Heavy-Lift Rocket!"));
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ],
5 | "packageRules": [
6 | {
7 | "groupName": "GraphQL Java (ignoring snapshot builds)",
8 | "matchPackagePrefixes": ["com.graphql-java:"],
9 | "allowedVersions": "/^[0-9]+\\.[0-9]+(\\.[0-9]+)?$/"
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/reviews-subgraph/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM openjdk:17
2 | EXPOSE 8081
3 |
4 | ARG JAR_FILE=build/libs/reviews.jar
5 | ADD ${JAR_FILE} app.jar
6 | ENTRYPOINT ["java","-jar","/app.jar"]
7 |
--------------------------------------------------------------------------------
/reviews-subgraph/README.md:
--------------------------------------------------------------------------------
1 | # Reviews Subgraph
2 |
3 | [Apollo Federation JVM](https://github.com/apollographql/federation-jvm) example subgraph implementation using [Spring GraphQL](https://docs.spring.io/spring-graphql/docs/current/reference/html/) that extends the federated `Product` type with `reviews` field.
4 |
5 | ```graphql
6 | type Product @key(fields: "id") @extends {
7 | id: ID! @external
8 | reviews: [Review!]!
9 | }
10 |
11 | type Review {
12 | id: ID!,
13 | text: String
14 | starRating: Int!
15 | }
16 | ```
17 |
18 | ### Running locally
19 | Build the application by running the following from the project directory:
20 |
21 | ```shell
22 | ./gradlew build
23 | ```
24 |
25 | > NOTE: in order to ensure you use the right version of Gradle we highly recommend to use the provided wrapper scripts
26 |
27 | Start the server:
28 |
29 | * Run `ReviewsApplication.java` directly from your IDE
30 | * Alternatively you can also use the Gradle Spring Boot plugin
31 |
32 | ```shell
33 | ./gradlew bootRun
34 | ```
35 |
36 | Once the app has started you can explore the example schema by opening GraphiQL endpoint at http://localhost:8081/graphiql.
37 |
--------------------------------------------------------------------------------
/reviews-subgraph/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.springframework.boot.gradle.tasks.bundling.BootJar
2 |
3 | plugins {
4 | id("org.springframework.boot") version "3.3.0"
5 | id("io.spring.dependency-management") version "1.1.3"
6 | java
7 | }
8 |
9 | group = "com.example"
10 | version = "0.0.1-SNAPSHOT"
11 |
12 | repositories {
13 | mavenCentral()
14 | }
15 |
16 | val federation_jvm_version: String = project.property("federation-jvm.version").toString()
17 | dependencies {
18 | implementation("com.apollographql.federation:federation-graphql-java-support:$federation_jvm_version")
19 | implementation("org.springframework.boot:spring-boot-starter-actuator")
20 | implementation("org.springframework.boot:spring-boot-starter-graphql")
21 | implementation("org.springframework.boot:spring-boot-starter-web")
22 | testImplementation("org.springframework.boot:spring-boot-starter-test")
23 | testImplementation("org.springframework.boot:spring-boot-starter-webflux")
24 | testImplementation("org.springframework.graphql:spring-graphql-test")
25 | }
26 |
27 | java {
28 | sourceCompatibility = JavaVersion.VERSION_17
29 | targetCompatibility = JavaVersion.VERSION_17
30 | }
31 |
32 | tasks.withType {
33 | useJUnitPlatform()
34 | }
35 |
36 | tasks.named("bootJar") {
37 | archiveFileName.set("reviews.jar")
38 | }
39 |
--------------------------------------------------------------------------------
/reviews-subgraph/src/main/java/com/example/reviews/ReviewsApplication.java:
--------------------------------------------------------------------------------
1 | package com.example.reviews;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer;
6 | import org.springframework.context.annotation.Bean;
7 | import org.springframework.graphql.data.federation.FederationSchemaFactory;
8 |
9 | @SpringBootApplication
10 | public class ReviewsApplication {
11 |
12 | public static void main(String[] args) {
13 | SpringApplication.run(ReviewsApplication.class, args);
14 | }
15 |
16 | @Bean
17 | public GraphQlSourceBuilderCustomizer customizer(FederationSchemaFactory factory) {
18 | return builder -> builder.schemaFactory(factory::createGraphQLSchema);
19 | }
20 |
21 | @Bean
22 | FederationSchemaFactory federationSchemaFactory() {
23 | return new FederationSchemaFactory();
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/reviews-subgraph/src/main/java/com/example/reviews/ReviewsController.java:
--------------------------------------------------------------------------------
1 | package com.example.reviews;
2 |
3 | import java.util.List;
4 |
5 | import com.example.reviews.model.Product;
6 | import com.example.reviews.model.Review;
7 | import com.example.reviews.model.ReviewSource;
8 |
9 | import org.springframework.graphql.data.federation.EntityMapping;
10 | import org.springframework.graphql.data.method.annotation.Argument;
11 | import org.springframework.graphql.data.method.annotation.SchemaMapping;
12 | import org.springframework.stereotype.Controller;
13 |
14 | @Controller
15 | public class ReviewsController {
16 |
17 | @EntityMapping
18 | public Product product(@Argument Long id) {
19 | return new Product(id);
20 | }
21 |
22 | @SchemaMapping
23 | public List reviews(Product product) {
24 | return ReviewSource.getReviews(product);
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/reviews-subgraph/src/main/java/com/example/reviews/model/Product.java:
--------------------------------------------------------------------------------
1 | package com.example.reviews.model;
2 |
3 | public record Product(Long id) {
4 | public static final String PRODUCT_TYPE = "Product";
5 | }
6 |
--------------------------------------------------------------------------------
/reviews-subgraph/src/main/java/com/example/reviews/model/Review.java:
--------------------------------------------------------------------------------
1 | package com.example.reviews.model;
2 |
3 | public record Review(String id, String text, Integer starRating) {
4 |
5 | public Review(String id, Integer starRating) {
6 | this(id, null, starRating);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/reviews-subgraph/src/main/java/com/example/reviews/model/ReviewSource.java:
--------------------------------------------------------------------------------
1 | package com.example.reviews.model;
2 |
3 | import java.util.Collections;
4 | import java.util.List;
5 | import java.util.Map;
6 |
7 | public final class ReviewSource {
8 |
9 | private static final Map> reviewMap = Map.of(
10 | 2L, List.of(new Review("1020", "Very cramped :( Do not recommend.", 2), new Review("1021", "Got me to the Moon!", 4)),
11 | 3L, List.of(new Review("1030", 3)),
12 | 4L, List.of(new Review("1040", 5), new Review("1041", "Reusable!", 5), new Review("1042", 5)),
13 | 5L, List.of(new Review("1050", "Amazing! Would Fly Again!", 5), new Review("1051", 5))
14 | );
15 |
16 | public static List getReviews(Product product) {
17 | return reviewMap.getOrDefault(product.id(), Collections.emptyList());
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/reviews-subgraph/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 8081
3 |
4 | spring:
5 | graphql:
6 | graphiql:
7 | enabled: true
8 | schema:
9 | printer:
10 | enabled: true
11 | logging:
12 | level:
13 | org.springframework.graphql: DEBUG
14 |
--------------------------------------------------------------------------------
/reviews-subgraph/src/main/resources/graphql/schema.graphqls:
--------------------------------------------------------------------------------
1 | type Product @key(fields: "id") @extends {
2 | id: ID! @external
3 | reviews: [Review!]!
4 | }
5 |
6 | type Review {
7 | id: ID!,
8 | text: String
9 | starRating: Int!
10 | }
11 |
--------------------------------------------------------------------------------
/reviews-subgraph/src/test/java/com/example/reviews/ReviewsApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.example.reviews;
2 |
3 | import com.example.reviews.model.Review;
4 | import java.util.List;
5 | import java.util.Map;
6 | import org.junit.jupiter.api.Test;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureHttpGraphQlTester;
9 | import org.springframework.boot.test.context.SpringBootTest;
10 | import org.springframework.graphql.test.tester.HttpGraphQlTester;
11 |
12 | @AutoConfigureHttpGraphQlTester
13 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
14 | public class ReviewsApplicationTest {
15 |
16 | @Autowired
17 | private HttpGraphQlTester tester;
18 |
19 | @Test
20 | public void verifiesProductQuery() {
21 | String query = """
22 | query Entities($representations: [_Any!]!) {
23 | _entities(representations: $representations) {
24 | ...on Product {
25 | id
26 | reviews {
27 | id
28 | text
29 | starRating
30 | }
31 | }
32 | }
33 | }
34 | """;
35 |
36 | tester.document(query)
37 | .variable("representations", List.of(Map.of("__typename", "Product", "id", "5")))
38 | .execute()
39 | .path("_entities[0].id").entity(String.class).isEqualTo("5")
40 | .path("_entities[0].reviews[0]")
41 | .entity(Review.class)
42 | .isEqualTo(new Review("1050", "Amazing! Would Fly Again!", 5));
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/router.yaml:
--------------------------------------------------------------------------------
1 | cors:
2 | # allow_credentials: true
3 | allow_any_origin: true
4 |
5 | health_check:
6 | listen: 0.0.0.0:8088
7 | enabled: true
8 |
9 | homepage:
10 | enabled: false
11 |
12 | sandbox:
13 | enabled: true
14 |
15 | supergraph:
16 | listen: 0.0.0.0:3000
17 | introspection: true
18 |
19 | include_subgraph_errors:
20 | all: true
21 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | mavenCentral()
4 | gradlePluginPortal()
5 | }
6 | }
7 |
8 | rootProject.name = "federation-jvm-spring-example"
9 |
10 | include(":products-subgraph")
11 | include(":reviews-subgraph")
12 |
--------------------------------------------------------------------------------
/supergraph.yaml:
--------------------------------------------------------------------------------
1 | federation_version: =2.3.5
2 | subgraphs:
3 | products:
4 | routing_url: http://products:8080/graphql
5 | schema:
6 | file: ./products-subgraph/src/main/resources/graphql/schema.graphqls
7 | reviews:
8 | routing_url: http://reviews:8081/graphql
9 | schema:
10 | file: ./reviews-subgraph/src/main/resources/graphql/schema.graphqls
11 |
--------------------------------------------------------------------------------