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
.
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]!
}
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.
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).
Let's go through the steps.
1. Client request
The client sends a request.
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
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.
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 }),
},
},
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
.
3. Response from storefront
From the previous request, the response from the storefront is as follows:
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.
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:
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:
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.
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 }),
},
},
This is implicitly injected into the query, as can be seen from the actual request below:
6. Response from products
From the previous request, the response from the products is as follows:
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):
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.
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)