Batch Resolution
Both node resolvers and field resolvers can be implemented using the batchResolve
function. This provides an alternative to the widely used data loader pattern.
The N+1 problem
Consider this example schema:
type Query {
recommendedListings: [Listing] @resolver
}
type Listing implements Node {
id: ID!
title: String
}
Suppose the query below returns 3 recommended listings. A Listing
node resolver that makes a call to a listings service to fetch a single listing in the resolve
function will result in 3 separate calls to the service.
query {
recommendedListings {
id
title
}
}
This is the N+1 problem, which is commonly solved by implementing a data loader that batches calls to the listings service. The resolver calls the data loader, which then calls the data source.
batchResolve
In Viaduct, you can implement the batchResolve
function and directly call the data source instead of going through a data loader. Under the hood, Viaduct still uses a data loader to batch requests. However, this data loader is part of Viaduct’s framework, not something that application developers need to write and maintain. Here’s an example Listing
batch node resolver:
@Resolver
class ListingNodeResolver @Inject constructor(val client: ListingClient) : NodeResolvers.Listing() {
override suspend fun batchResolve(contexts: List<Context>): List<FieldValue<Listing>> {
val listingIDs = contexts.map { it.id.internalID }
val responses = client.fetch(listingIDs)
return contexts.map { ctx ->
val listingID = ctx.id.internalID
val response = responses[listingID]
FieldValue.ofValue(
Listing.Builder(ctx)
.title(response.title)
.build()
)
}
}
}
Input
batchResolve
takes a list of Context
objects as input. This is the same Context
object type passed to the non-batching resolve
function. Viaduct’s GraphQL execution engine batches these contexts before passing them to the batchResolve
function.
Output
The list that batchResolve
returns must have the same number of elements as the input list. Each output value corresponds to the input Context
at the same list index.
FieldValue
Notice that the output list elements are wrapped in FieldValue
(e.g., List<FieldValue<Listing>>
in the example above). This represents either a successfully resolved value or an error value.
Usage:
FieldValue.ofValue(v)
: constructs a successfully resolved value, as shown in the example aboveFieldValue.ofError(e)
: constructs an error value, wheree
is an exception. The corresponding value in the GraphQL response will be null, and there will be an error in the errors array.
When to use batchResolve
Override batchResolve
whenever you need to fetch data from an external data source that supports batch loading. This solves the N+1 problem and similar issues where multiple parts of a GraphQL query fetch data that can be batched together.
If your resolver does not have external data dependencies, there is generally no benefit to implementing batchResolve
.
Those familiar with data loaders may know that they also provide an intra-request cache. In Viaduct, this memoization cache is decoupled from batching, so you do not need to implement batchResolve
for caching purposes.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.