HandbookOther integrationsFederation to Stitching SDL

Federation to Stitching SDL

This example demonstrates the integration of Apollo Federation services into a stitched schema.

As you get the hang of schema stitching, you may find that Federation services are fairly complex for what they do. The buildFederatedSchema method from the @apollo/federation package creates a nuanced GraphQL resource that does not guarentee itself to be independently consistent, but plugs seamlessly into a greater automation package. By comparison, stitching encourages services to be independently valid and self-contained GraphQL resources, which makes them quite primitive and durable. While Federation automates service bindings at the cost of tightly-coupled complexity, stitching embraces loosely-coupled bindings at the cost of manual setup. The merits of each strategy are likely to be a deciding factor for developers selecting a platform. Stitching is a library used to build a framework like Federation.

Stitching is less opinionated than Federation, and is made considerably simpler without the complexity added by buildFederatedSchema. However, when integrating with existing servers or in the process of a migration, nothing says you can’t incorporate your existing federation resources into a stitched gateway going through the federation _entities query – which is fundamentally just a GraphQL service.

This example demonstrates:

  • Integrating Apollo Federation services into a stitched schema.
  • Fetching and parsing Federation SDLs.

Sandbox

⬇️ Click ☰ to see the files

You can also see the project on GitHub here.

The following services are available for interactive queries:

  • Stitched gateway: listening on 4000/graphql
  • Products subservice: listening on 4001/graphql
  • Reviews subservice: listening on 4002/graphql
  • Users subservice: listening on 4003/graphql

This example is based on the Federation intro example.

Summary

Ever wonder what Federation is doing under the hood? Visit the products service and check out some User objects:

query {
  _entities(
    representations: [
      { __typename: "User", id: "1" }
      { __typename: "User", id: "2" }
      { __typename: "User", id: "3" }
    ]
  ) {
    ... on User {
      id
      recentPurchases {
        upc
        name
        price
      }
    }
  }
}

A federation service automatically configures an _entities query that recieves typed keys (i.e.: objects with a __typename), and returns abstract _Entity objects that may assume the shape of any type in the service. Apollo Gateway then automates the exchange of typed keys for typed results, all going through the dedicated _entities protocol in each subservice. Stitching can also integrate with this _entities query by sending it properly formatted keys.

Now go to the gateway and check out the stitched results:

query {
  user(id: "1") {
    username
    recentPurchases {
      upc
      name
    }
    reviews {
      body
      author {
        id
        username
      }
      product {
        upc
        name
        acceptsNewReviews
      }
    }
  }
}

The stitched gateway has loaded all federation SDLs, converted them into stitching SDLs, and then integrates them like any other GraphQL service with types merged through their _entities query.

Adapting Federation services

Federation and Stitching use fundamentally similar patterns to combine underlying subservices (in fact, both tools have shared origins in Apollo Stitching). However, their specific implementations have an important differentiator:

  • Apollo Federation uses a centralized approach, where all types have a single “origin” service (i.e.: where the unextended type definition is). Querying for a type builds from its origin service.
  • Stitching uses a decentralized approach, where any service may equally originate any type. Regardless of where a typed object is first represented, that original object is filled in with missing details from other services.

How each system handles origins informs how a federation service gets translated into a stitched subschema:

  1. All types with a @key directive become merged types; the key fields go into selectionSet.
  2. All fields with a @requires directive are made into computed fields. Computed fields are slightly more robust than their federation counterparts because they may resolve dependencies from any number of services.
  3. All fields with an @external directive are removed unless they are part of the @key. Stitching expects schemas to only publish fields that they actually have data for. This is considerably simpler than the federation approach where services may be responsible for data they don’t have.
  4. By eliminating the indirection of @external fields, the @provides directive is no longer necessary. The Stitching query planner can automate the optimial selection of as many fields as possible from as few services as possible.

SDL integration

The simplest way to make the above adaptions is to translate a Federation SDL string into a Stitching SDL string, which can be done using the federationToStitchingSDL utility function from @graphql-tools/stitching-directives package. A federation service’s SDL can be obtained through its _service API:

query {
  _service {
    sdl
  }
}

Once fetched, it can be translated into a Stitching SDL and then built into a stitched schema:

import { buildSchema } from 'graphql'
import { buildHTTPExecutor } from '@graphql-tools/executor-http'
import { federationToStitchingSDL, stitchingDirectives } from '@graphql-tools/stitching-directives'
 
const stitchingConfig = stitchingDirectives()
 
const executor = buildHTTPExecutor({ endpoint: 'http://localhost:4001/graphql' })
const federationSDL = await executor({
  document: parse(/* GraphQL */ `
    {
      _service {
        sdl
      }
    }
  `)
})
const stitchingSDL = federationToStitchingSDL(federationSDL, stitchingConfig)
 
const gatewaySchema = stitchSchemas({
  subschemaConfigTransforms: [stitchingConfig.stitchingDirectivesTransformer],
  subschemas: [
    {
      schema: buildSchema(stitchingSDL),
      executor
    }
  ]
})

Static config

Written as static subservice configuration, a federation service merges types within a stitched gateway using the following:

import { pick } from 'lodash'
 
const gatewaySchema = stitchSchemas({
  subschemas: [
    {
      schema: buildSchema(stitchingSDL),
      merge: {
        Product: {
          selectionSet: '{ id }',
          fields: {
            shippingEstimate: { selectionSet: '{ price weight }', computed: true }
          },
          fieldName: '_entities',
          key: originObj => ({
            __typename: 'Product',
            ...pick(originObj, ['id', 'price', 'weight'])
          }),
          argsFromKeys: representations => ({ representations })
        }
      }
    }
  ]
})