├── .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 | --------------------------------------------------------------------------------