Array-batched type merging
This example explores the core techniques for merging typed objects using array queries, covering most of the topics discussed in batched merging documentation.
This example focuses on array batching—meaning that all records accessed during a round of delegation are batched together and loaded as an array. This technique greatly reduces the execution overhead of single-record merges, and can be further optimized by enabling query batching. This array-batched strategy is prefereable to single-record merges and should be used whenever possible.
This example demonstrates:
- One-way type merging using array queries.
- Multi-directional type merging using array queries.
- Handling array errors.
- Nullability & error remapping.
Related examples:
- See single-record type merging for additional information on basic merge patterns.
- See stitching directives SDL to write this merge configuration as schema directives.
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
- Manufacturers subservice: listening on 4001/graphql
- Products subservice: listening on 4002/graphql
- Storefronts subservice: listening on 4003/graphql
Summary
Visit the stitched gateway and try running the following query:
query {
storefront(id: "2") {
id
name
products {
upc
name
manufacturer {
products {
upc
name
}
name
}
}
}
}
If you study the results of this query, the final composition traverses across the service graph:
Storefront
(Storefronts schema)Storefront.products -> Product
(Products schema)Product.manufacturer -> Manufacturer
(Products + Manufacturers schemas)Manufacturer.products
(Products schema)Manufacturer.name
(Manufacturers schema)
That means the gateway performed three rounds of resolution for each service’s data
(Services -> Products -> Manufacturers
). With the array-batching technique, the gateway only
performs a single delegation per round, regardless of the number of records in the round.
Error handling
Pay special attention to the error handling used while resolving record arrays:
manufacturers(root, { ids }) {
return ids.map(id => manufacturers.find(m => m.id === id) || new NotFoundError());
}
It is extremely important that errors get mapped into the result set, rather than being thrown (which corrupts the entire result set).
Nullability + mapped errors
Also run a query for the Product with UPC "6"
, and you’ll see an interesting feature of error
handling:
query {
products(upcs: ["6"]) {
upc
name
manufacturer {
name
}
}
}
For the purposes of this example, this product intentionally specifies an invalid manufacturer reference. You’ll see that the original error from the underlying subservice has flowed through the stitching process and is mapped to its final document position in the stitched schema:
{
"errors": [
{
"message": "Record not found",
"locations": [],
"path": ["products", 0, "manufacturer"],
"extensions": {
"code": "NOT_FOUND"
}
}
],
"data": {
"products": [
{
"upc": "6",
"name": "Baseball Glove",
"manufacturer": null
}
]
}
}
Note that for this process to work, the Product.manufacturer
reference must be
nullable, otherwise you’ll get a GraphQL
nullability-mismatch error when the manufacturer returns an error. For this reason, it’s generally
best-practice to make all stitched associations nullable on the assumption that the record
association could fail, at which time it’s better to see the subschema failure than a top-level
GraphQL nullability error.