GraphQL is great for when you want to combine all your data into one single API, without having to deal with different response formats. When you're looking to combine data from these different backends, you want to limit the number of calls between these backends to collect all the data you need.
IBM API Connect Essentials (formerly StepZen), added a custom directive called @supplies to GraphQL. You can define a root Query or Mutation field to return an abstract type (interface or union). If the field is not annotated with a backend directive (such as @rest), then it can be resolved indirectly through the @supplies custom directive.
The @supplies custom directive allows you to separate an API from its implementation. For example, an order schema can define an abstract field that provides package tracking without defining which delivery services provide that information. Then, different deployments of the GraphQL schema that uses the same API can provide implementations.
In this article, you learn how to use the @supplies custom directive through an example, where you're implementing GraphQL for different delivery tracking services. Imagine one of these services supports “Fast Package” delivery while another supports “Fast Package” and “Rain or Shine” delivery.
Single supplying field
In this first example, we are going to add tracking information to a customer orders schema.
First, Define a simple interface that contains information about a delivery and a Query field.
Then, extend the Order type to include delivery information by using the @materializer directive.
extend type Order {expected:Delivery @materializer(query:"expected"arguments:{name:"id"field:"trackingId"})}
Show more
Now, the schema can be deployed but any selection of Order.expected will return null as there is no implementation for Query.expected. At this point, you can add the @supplies custom directive.
Independently, you need to define a type for the “Fast Package” delivery service.
"""
Delivery by FastPackage.
"""type FastPackage implements Delivery {when: Date
note: String
distance: Int
}
Show more
FastPackage implements Delivery but has an additional field with information about how far the package is from its destination.
Then, you need to define a field that calls the “Fast Package” REST API and indicates that it suppliesQuery.delivery:
Now when Query.expected or Order.expected is selected the field is resolved automatically by a selection of Query.fp.
A request can access the additional information by using a fragment:
{
customer(id:1){
name
orders {
trackingId
delivery {
when
note
...on FastPackage {
distance
}}}}
Show more
Multiple supplying fields
But what if you need to support multiple package tracking services? The @supplies custom directive allows multiple "concrete" fields to supply an abstract field, so we can extend our schema further with an additional delivery service called “Rain or Shine.”
"""
Delivery by RainOrShine.
"""type RainOrShine implements Delivery {when: Date
note: String
weather: String
}
Show more
Again, the RainOrShine interface implements Delivery, but this time it has different information about the weather at the delivery address when the package is expected.
Now, when Query.expected or Order.expected is selected, the field is resolved automatically by a selection of both Query.fp and Query.ros fields, which results in a call to each delivery service's REST API.
A request can access the additional information from each service using fragments:
{
customer(id:1){
name
orders {
trackingId
delivery {
when
note
...on FastPackage {
distance
}...on RainOrShine {
weather
}}}}
Show more
The supplying fields can use any backend type, one could be @rest, one could be @graphql, and two others could use @dbquery.
Because expected is a singleton of Delivery, only one value can be returned and so API Connect Essentials selects the "best" value.
In our example, because a tracking identifier is specific to a service, one call would return a valid object while the other would return null. So, the "best" value would be the valid object and not the null value.
If the interface field instead was a list of the abstract type, then the list will return all non-null values from the supplying fields. If the supplying fields also return lists, then the lists from the supplying fields are combined into a single list.
Routing data
In some cases, calling out to all supplying fields is correct. However, in this delivery example, it is not correct because:
Tracking identifiers for one delivery service are sent to its competitors, which results in leaking information.
Additional pointless REST API calls are made to delivery services that are not handling the package, which might result in incurring per-call costs.
To address this scenario, you can use the @supplies custom directive, which supports routing by using an if argument with a script that represents a Boolean expression.
To simplify the example, we assume that tracking identifiers have a prefix that indicates the delivery service, so we add an if argument to each of the @supplies directives.
Now, when Query.expected or Order.expected is selected, only one of Query.fp or Query.ros will be called, depending on the tracking identifier.
If neither routing condition is matched, then no fields are called and null is returned.
The script has access to the field's arguments as variables (id in this case).
Here, the if condition is ECMAScript (default) syntax, but JSONata syntax is also supported.
Summary and next steps
In this article, you learned how to use the @supplies custom directive to route data in GraphQL, such as when you implement tracking for multiple delivery services in a single GraphQL schema.
You can find a complete overview of the code that we used in this article in this GitHub repo.
Questions? We'd love to connect with you on Discord.
About cookies on this siteOur websites require some cookies to function properly (required). In addition, other cookies may be used with your consent to analyze site usage, improve the user experience and for advertising.For more information, please review your cookie preferences options. By visiting our website, you agree to our processing of information as described in IBM’sprivacy statement. To provide a smooth navigation, your cookie preferences will be shared across the IBM web domains listed here.