Handbook
Foundation
Type merging with multiple keys

Type merging with multiple keys

This example explores merging types with multiple key fields. This is a tricky scenario where an intermediary service provides multiple keys that join other services together, while the other services each only have one of the possible keys. For example:

  • Catalog service: type Product { upc }
  • Vendors service: type Product { upc id }
  • Reviews service: type Product { id }

In the above graph, Catalog and Reviews schemas each have a different key for the Product type, while the Vendors service holds both keys and may act as an intermediary join. Given this architecture, we must still be able to perform all possible traversals:

  • Catalog > Vendors > Reviews
  • Catalog < Vendors > Reviews
  • Catalog < Vendors < Reviews

Stitching is capable of handling this service pattern using the entryPoints property in static JavaScript merge configuration. At present, this property is not configurable using SDL directives, but can be worked around.

This example demonstrates:

  • Configuring multiple key entry points for a subschema.

Sandbox

⬇️ Click ☰ to see the files

You can also see the project on GitHub here.

The following service is available for interactive queries:

  • Stitched gateway: listening on 4000/graphql

For simplicity, all subservices in this example are run locally by the gateway server. You could easily break out any subservice into a standalone remote server following the combining local and remote schemas example.

Summary

Visit the stitched gateway and try running the following query that fetches data from all services following each possible service traversal path:

query {
  # catalog service
  productsByUpc(upcs: ["1"]) {
    upc
    name
    retailPrice
    reviews {
      id
      body
    }
  }
 
  # vendors service
  productsByKey(keys: [{ upc: "1" }, { id: "101" }]) {
    id
    upc
    name
    retailPrice
    reviews {
      id
      body
    }
  }
 
  # reviews service
  productsById(ids: ["101"]) {
    id
    name
    retailPrice
    reviews {
      id
      body
    }
  }
}

The above queries entering the graph through the Catalog and Reviews services use whatever key they have available to fetch from the intermediary Vendors service, and Vendors provides the missing key for fetching from the opposing service.

Multiple entryPoints

To facilitate this pattern, the Vendors subschema configures the merged Product type with multiple entry points – or, separate pathways that access the type using different criteria:

{
  schema: vendorsSchema,
  batch: true, // << enable batching to consolidate requests!
  merge: {
    Product: {
      entryPoints: [{
        selectionSet: '{ upc }',
        fieldName: 'productsByKey',
        key: ({ upc }) => ({ upc }),
        argsFromKeys: (keys) => ({ keys }),
      }, {
        selectionSet: '{ id }',
        fieldName: 'productsByKey',
        key: ({ id }) => ({ id }),
        argsFromKeys: (keys) => ({ keys }),
      }],
    }
  }
}

In this example, both entry points reference the productsByKey query with specially-tailored arguments. You could just as easily have each entry point reference a different root query that accepts different argument formats.

When using multiple entry points, it is highly recommended that you enable query batching (batch: true). This is generally good practice, and becomes particularly important for consolidating requests that may target either entry point.

With SDL directives

At present, there is not yet an SDL directives pattern that configures multiple entry points (contributions are welcome!). However, SDL configuration and static JavaScript configuration patterns are not mutually exclusive, therefore you can use SDL directives to configure the majority of your API and statically configure the cases where a merged type has multiple entry points. The general pattern looks like this:

const { stitchingDirectivesTransformer } = stitchingDirectives()
 
const gatewaySchema = stitchSchemas({
  subschemaConfigTransforms: [stitchingDirectivesTransformer],
  subschemas: [
    {
      schema: catalogSchema
      // merge: { ... built from SDL directives }
    },
    {
      schema: vendorsSchema,
      batch: true,
      merge: {
        // Type1: { ... built from SDL directives },
        // Type2: { ... built from SDL directives },
        Product: {
          entryPoints: [
            {
              selectionSet: '{ upc }',
              fieldName: 'productsByKey',
              key: ({ upc }) => ({ upc }),
              argsFromKeys: keys => ({ keys })
            },
            {
              selectionSet: '{ id }',
              fieldName: 'productsByKey',
              key: ({ id }) => ({ id }),
              argsFromKeys: keys => ({ keys })
            }
          ]
        }
      }
    },
    {
      schema: reviewsSchema
      // merge: { ... built from SDL directives }
    }
  ]
})

The static configuration you define is simply augmented by the stitching directives transformer. There’s no harm in configuring some types statically and adding SDL configurations on top. Long-term plans anticipate adding multiple entry points setup into the SDL directives package.