DEV Community

Cover image for Deciphering GraphQL: A Deep Dive into Type Merging and Schema Stitching
Ken Fukuyama
Ken Fukuyama

Posted on

Deciphering GraphQL: A Deep Dive into Type Merging and Schema Stitching

Recently, I've been studying GraphQL Stitching, and no matter how many times I read about Type Merging, it didn't quite click. So, I made an effort to fully understand it.

The official documentation for Type Merging can be found here:

https://the-guild.dev/graphql/stitching/docs/approaches/type-merging

Assumptions for this article

In this article, I won't be explaining what GraphQL Gateway or GraphQL Stitching are. 🙏 There are plenty of good articles explaining those aspects, so I recommend checking them out first.

For reference, the official documentation can be found here:

https://the-guild.dev/graphql/stitching

Type Merging in GraphQL Stitching

What is Type Merging?

To put it succinctly, Type Merging is a mechanism that allows us to treat multiple schemas defining the same type as if they were just one type.

For example, let's say there's a GraphQL Gateway, and behind it, there are GraphQL servers handling manufacturers, products, and storefronts.

Image description

Although the servers are separate, you'll notice that types like Product and Manufacturer are defined on each. These can be merged, and the Gateway can provide the following schema:

type Query {
  manufacturer(id: ID!): Manufacturer
  product(upc: ID!): Product
  _manufacturer(id: ID!): Manufacturer
  storefront(id: ID!): Storefront
}

type Manufacturer {
  id: ID!
  name: String!  # From the manufacturers schema
  products: [Product]!  # From the products schema
}

type Product {  # Mainly from the products schema
  upc: ID!
  name: String!
  price: Float!
  manufacturer: Manufacturer
}

type Storefront {  # From the storefronts schema
  id: ID!
  name: String!
  products: [Product]!
}
Enter fullscreen mode Exit fullscreen mode

Up to this point, it's relatively intuitive, but the "How do we merge?" part felt somewhat elusive when I read the documentation. I kept reading and forgetting, so I decided to take a closer look at the official sample's inner workings.

https://github.com/ardatan/schema-stitching/tree/master/examples/type-merging-single-records

Verifying the Merging Flow in practice

When you check the official documentation's MergingFlow, there's a diagram and a description of each step, explaining how the merging happens.

Image description

The diagram slightly omits certain explanations, making it somewhat challenging to grasp at a glance.

Thus, I visualized the flow in more detail using the Storefront, Product, and Manufacturer examples. (For a fullscreen view, click here).

Image description

Let's go through the steps.

1. Client request

The client sends a request.

Image description

Since the Gateway schema matches the one mentioned above, this is a regular GraphQL query, simply requesting the desired data.

2. The original request goes to the storefront server

Image description

Next, since the request is intended for the storefront, it's sent there via the gateway. At this point, the gateway filters the query, sending only the parts relevant to the storefront. The storefront only understands the Product type with just the upc, so the original query's name and manufacturer are omitted.

Image description

However, if we only had this, we'd lose the information we want to retrieve from the products, and we wouldn't be able to fetch data from the other servers. This is where selectionSet comes in. Citing the official documentation:

selectionSet specifies one or more key fields required from other services to perform this query. Query planning will automatically resolve these fields from other subschemas in dependency order.

In essence, it's declaring, "To get data for type ○○, we at least need △△ information." In this case, the products server has a configuration like:

merge: {
  Product: {
    // This service provides _all_ unique fields for the `Product` type.
    // Again, there's unique data here so the gateway needs a query configured to fetch it.
    // This config delegates to `product(upc: $upc)`.
    selectionSet: '{ upc }',
    fieldName: 'product',
    args: ({ upc }) => ({ upc }),
  },
},
Enter fullscreen mode Exit fullscreen mode

You can see a selectionSet of { upc }. It's declaring, "When merging data for the Product type, we need the upc." Having this information, the gateway implicitly adds upc when sending a request to the storefront server. Checking the actual query log confirms the presence of upc.

Image description

3. Response from storefront

From the previous request, the response from the storefront is as follows:

Image description

4. Creation of the merger query

Once the storefront responds, the next step is to send a request to the products server. Someone responsible for merging (let's call them the "merger") sends the query, utilizing the merge setting.

Image description

Here, the products server's merge setting specified for the gateway is in focus. Particularly, note the fieldName and args. This means that when merging the Product type, the server uses its product query, and for the argument, it uses the originating object's upc property.

It's a bit tricky, but the relationship is as shown below:

Image description

Using this information, the query for step 5 is generated.

5. Request to the products

server

Based on the information from step 4, a query is generated for the products server, which looks like this:

Image description

Thanks to the args definition, the upc is injected with the value 6.

From here on, it becomes repetitive, but only the query aspects that the products server can handle are filtered. The original query requested the manufacturer's name and products, but the products server isn't aware of the name property.

Image description

Thus, name is omitted.

For the Manufacturer type, there's a selectionSet of { id } set for the manufacturer server, as seen below:

merge: {
  // This schema provides one unique field of data for the `Manufacturer` type (`name`).
  // The gateway needs a query configured so it can fetch this data...
  // this config delegates to `manufacturer(id: $id)`.
  Manufacturer: {
    selectionSet: '{ id }',
    fieldName: 'manufacturer',
    args: ({ id }) => ({ id }),
  },
},
Enter fullscreen mode Exit fullscreen mode

This is implicitly injected into the query, as can be seen from the actual request below:

Image description

6. Response from products

From the previous request, the response from the products is as follows:

Image description

7,8,9. Similar requests are sent to the manufacturer server

I'll skip the details for the manufacturer server request, as it's repetitive. It looks like this (for a full view, click here):

Image description

10,11. All results are merged into the appropriate type and returned

Finally, all the results are merged, and the client receives the response they requested.

Image description

Conclusion

After delving deeper than the official documentation, I feel like I've come to grasp Type Merging much better. I particularly had a hard time understanding selectionSet, but seeing the actual requests helped me understand its implicit injection.

While I only touched on a small part about GraphQL Gateway, I believe others might also struggle to understand Type Merging, so I hope this article helps someone out there.

Top comments (0)