GraphQL Federation

Introduction

Apollo Federation provides a mechanism to combine multiple GraphQL endpoints and schema into a single aggregate endpoint and composite schema. The basic principles of GraphQL federation are as follows:

  1. Collects the schemas from multiple endpoints.

  2. Combines the schemas into a single composite schema.

  3. Serves the single composite schema to the user completely hiding the inner dependencies.

  4. When a query is performed, the Federation Gateway calls each of the endpoints in a specific order and combines their responses.

Quick Start

Download this docker-compose.yaml that manages all the required services for an aggregate federated SWAPI GraphQL endpoint that joins Semantic Objects and a simple example Apollo Server:

  • Federation Gateway

  • Semantic Objects Service (loaded with SWAPI data and Schema)

  • Example Extended SWAPI Service

  • GraphDB (loaded with SWAPI data)

  • MongoDB (for storing and managing the schema)

Once you have downloaded the compose file, follow the Semantic Objects Quick Start guide using this file instead of the one defined in the guide. (Skip the download operation in the Docker Compose section of the guide.)

Use this environment file .env to configure the location of the Ontotext Platform and GraphDB licenses.

Following the Quick Start guide, create a simple bash script setup.sh for starting everything and loading the SWAPI data and schema.

Download the GraphDB repository configuration, SWAPI data, and SOML schema and place them in the same directory as the setup script. You should have the following files in the directory: data.ttl, repo.ttl, and schema.yaml.

Run the setup script. To start the services, load the data, and build a federated aggregate GraphQL endpoint, use the following shell command:

chmod +x ./setup.sh && ./setup.sh

Note

The setup script and the .env file should be in the same directory as the docker-compose.yaml file.

Try out a GraphQL query against the federated aggregate schema using this cURL command:

curl 'http://localhost:9000/graphql' \
      -H 'Accept: application/json' \
      -H 'Content-Type: application/json' \
      --data-binary '{"query":"query { planet(where: {ID: \"https://swapi.co/resource/planet/25\"}) { name diameter gravity } }"}' \
      --compressed

That returns the Planet Dantooine with the extended calculated gravity property.

{
  "data": {
    "planet": [
      {
        "name": "Dantooine",
        "diameter": 9830,
        "gravity": "1 standard"
      }
    ]
  }
}

Tip

You can also open the OTP Workbench on http://localhost:9993. It is configured to send queries against the Apollo Gateway.

Tutorial

You can find more details on how to use Semantic Objects in this tutorial.

Limitations

  • The @key directive for all types is set to their ID, although the Apollo Federation server supports each field to be used as key.

  • The current Apollo Gateway implementation (ontotext/apollo-federation-gateway) will not update the aggregated schema if an endpoint’s schema changes. So changing the schema of a federated endpoint requires a Gateway restart.

Schema changes

When a schema change occurs in the Ontotext Platform or other external services used by the Apollo gateway, it must be restarted in order to get the combined schema rebuilt. In case the Apollo gateway does not start properly, please check its logs for more information about what might be wrong with the combined schema.

Administration

Monitoring

Health Check

The Apollo Federation Gateway has an aggregate health check that checks all configured federated services for health status.

The __health endpoint can be accessed at http://{federationhost}:{federation_port}/__health.

An example response is as follows, and you can see the Semantic Objects health response aggregated with the extended service:

{
  "status": "OK",
  "id": "apollo-federation-gateway",
  "healthChecks": [
    {
      "status": "OK",
      "healthChecks": [
        {
          "status": "OK",
          "id": "1100",
          "name": "MongoDB checks",
          "type": "mongo",
          "impact": "MongoDB operating normally and collection available.",
          "troubleshooting": "http://semantic-objects:8080/__trouble",
          "description": "MongoDB checks.",
          "message": "MongoDB operating normally and collection available."
        },
        {
          "status": "OK",
          "id": "1200",
          "name": "SPARQL checks",
          "type": "sparql",
          "impact": "SPARQL Endpoint operating normally, writable and populated with data.",
          "troubleshooting": "http://semantic-objects:8080/__trouble",
          "description": "SPARQL Endpoint checks.",
          "message": "SPARQL Endpoint operating normally, writable and populated with data."
        },
        {
          "status": "OK",
          "id": "1300",
          "name": "SOML Checks",
          "type": "soml",
          "impact": "SOML bound, service operating normally.",
          "troubleshooting": "http://semantic-objects:8080/__trouble",
          "description": "SOML checks.",
          "message": "SOML bound, service operating normally."
        },
        {
          "status": "OK",
          "id": "1350",
          "type": "soml-rbac",
          "impact": "Security is disabled. SOML RBAC healthcheck is inactive.",
          "troubleshooting": "http://semantic-objects:8080/__trouble",
          "description": "SOML RBAC checks.",
          "message": "Security is disabled. SOML RBAC healthcheck is inactive."
        },
        {
          "status": "OK",
          "id": "1400",
          "name": "Query service",
          "type": "queryService",
          "impact": "Query service operating normally.",
          "troubleshooting": "http://semantic-objects:8080/__trouble",
          "description": "Query service checks.",
          "message": "Query service operating normally."
        },
        {
          "status": "OK",
          "id": "1500",
          "name": "Mutations Service",
          "type": "mutationService",
          "impact": "Mutation Service operating normally.",
          "troubleshooting": "http://semantic-objects:8080/__trouble",
          "description": "Mutation Service checks.",
          "message": "Mutation Service operating normally."
        }
      ]
    },
    {
      "status": "OK",
      "id": "example/1",
      "name": "Example Health Check",
      "type": "graphql",
      "healthChecks": []
    }
  ]
}

Good to Go

The Apollo Federation Gateway has an aggregate good to go check that checks all configured federated services for good to go status.

The __gtg endpoint can be accessed at http://{federationhost}:{federation_port}/__gtg.

An example response is as follows:

{
  "gtg": "OK",
  "message": "Federation Gateway operating as expected"
}

Configuration

Federation Gateway

The Federation Gateway is provided as a docker container. The following docker-compose YAML describes the various ENV variables that control the Gateway:

  • GRAPHQL_SERVICE_N: GraphQL endpoint URL address to include within the aggregate schema

  • GRAPHQL_SERVICE_N_GTG: GraphQL endpoint good to go endpoint

  • GRAPHQL_SERVICE_N_HEALTH: GraphQL endpoint health endpoint

  • CACHE_MAX_AGE: cache header max age

  • ENGINE_API_KEY: in case you want to register the federation services with an Apollo Graph Manager

  • SERVICE_DWELL: dwell time between federated GraphQL endpoint health checks

  • POLL_INTERVAL: the interval between federated GraphQL endpoint health checks (start-up)

You can download and use this example docker-compose.yaml configuration.

Federation Gateway Authorization

If the Platform (Semantic Objects) has security enabled, the Federation Gateway must authenticate and retrieve an Authorization JWT token.

This token is used by the Gateway to make authorized requests to federated services to retrieve each GraphQL _service.sdl schema.

After that, the Gateway builds the aggregate schema and discards the token.

Subsequent GraphQL query and mutation requests will use the client’s Authorization header.

The following ENV variables control the Gateway schema aggregation authorization:

  • SECURITY_ENABLED: true - schema building uses FusionAuth supplied authorization tokens, otherwise no authorization

  • FUSION_APPLICATION_ID: The FusionAuth Application UUID for the Platform, e.g., “0afceebe-cfc6-4ddf-81b9-a7fd658b30d4”

  • FUSION_PROTOCOL: The protocol to use when connecting to the FusionAuth REST API “http” | “https”

  • FUSION_HOST: The server host to use when connecting to the FusionAuth REST API, e.g., “localhost”

  • FUSION_PORT: The TCP/IP port to use when connecting to the FusionAuth REST API, e.g., “9011”

  • FUSION_USERNAME: The FusionAuth username to use when authenticating, e.g., “federation”

  • FUSION_PASSWORD: The FusionAuth password to use when authentication, e.g., “federation”

  • FORWARD_HEADERS: The HTTP headers the Gateway should forward to federated GraphQL endpoints “X-Request-ID, Authorization”

  • FUSION_LOGIN_RETRIES: The number of authentication re-tries before giving up. Default “10”

  • FUSION_LOGIN_RETRY_DELAY: The number of milliseconds between each authentication retry. Default “2000”

As mentioned above, in order for the Federation Gateway to retrieve the SDL, it needs a user with sufficient rights to do so. So first, add the following role to the SOML (it provides gives read permissions to the SDL):

1
2
3
4
5
6
7
rbac:
  roles:
    Federation:
      description: "Federation service role"
      actions: [
        "_Service/sdl/read"
      ]

The next step is to add the user in FusionAuth. The user should have role Federation and username and password matching the ones defined in FUSION_USERNAME and FUSION_PASSWORD environment variables.

If you are using the OPCTL tool to provision the security, add the following to the configuration yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
security:
  roles:
    - name: Federation
      superRole: false
      default: false
  users:
    - username: federation
      email: federation@example.com
      password: federation
      roles:
        - Federation

Once you have a configured user and role, the Federation Gateway will be able to operate properly.

Federation Correlation

The Federation Gateway will generate X-Request-ID correlation ids if none are present on requests. X-Request-ID is always logged and forwarded to federated GraphQL endpoints.

This ensures that a GraphQL query or mutation can be traced and correlated across the Gateway, Federated GraphQL services and indeed GraphDB.

By default, the docker file should include the following headers for forwarding:

  • FORWARD_HEADERS: “X-Request-ID, Authorization”

X-Request-ID is the Platform default correlation id HTTP header.

Semantic Objects Service

SOaaS supports the Apollo Federation protocol and can be a part of an Apollo Federation (with some known limitations).

To make the SOaaS endpoint federation-ready, you need to enable the following property:

graphql.federation.enabled=true

This will make all necessary additions to the GraphQL schema.