Why You Should Not Pass Input Parameters Using DGS Custom Context

Matan Gilad
Fiverr Tech
Published in
5 min readApr 26, 2022

At Fiverr, we are using Kotlin with Spring Boot microservices for our backend. As part of our architecture revolution we wanted to use GraphQL (you can read here about our journey). Since our system is already well divided into microservices, we decided to go with Apollo Federation. The natural choice for our GraphQL server implementation was Netflix’s DGS.

In this article, we will share an interesting scenario that could help developers who are just starting with GraphQL in Kotlin by using Netflix’s DGS .

The Scenario

To begin with an example, let’s say that we have a Users service in which we want to implement a GraphQL API.

Let’s also say that we want to add a query to fetch users by username. Let’s define the query for that:

Now, let’s say that a user has an ID, username, and friends. The ID and username are primitive fields, but a user can have many friends. Accordingly, we would like to be able to paginate through them. Let’s define the schema for that:

Then, we would need to implement the resolvers that resolve the data for the response. Since most of the fields are quite simple, most of the resolvers are quite simple as well. However, there is one more complicated field which is the field of friends in User type. That is because the friends field has input parameters and is paginated. It also requires another call to fetch the users which means that a data loader should be used to avoid the N+1 problem.
Accordingly, we will need to implement a data loader to fetch the friends of all users. The data loader should have access to the input parameters that the client sent in the request.

So, wouldn’t it be nice in our case to have the input parameters available anywhere we need? Certainly! Fortunately, DGS has supplied a solution for that called DGS custom context.

DGS Custom Context

As DGS describes in the Data Fetching Context section, “Each data fetcher in [GraphQL] Java has a context.” The DGS framework goes one step further and allows developers using it to extend the context with their own custom context. You can read all about it with examples in the Data Fetching Context section, but the main benefit is that you can create a class that holds data on the context of the request and access that class from a data fetcher or a data loader.

The Issue with Passing Input Parameters in DGS Custom Context

Imagine that the developer of the Users service responsible for resolving the usersByUsernames query has decided to pass the input parameters as part of the custom context. It would look something like this:

Additionally, the initialization in the resolver would look like this:

Then, the data loader can fetch the input parameters from the DGS custom context:

So what’s wrong with this approach? After all, it seems pretty easy — you don’t need to think about how to transfer data between different areas of the code responsible for fetching data. Instead, it just exists everywhere!

The problem becomes clear when we examine the next client use case:

A Cool Use Case

Let’s say that our clients want to get users by their usernames in order to open a bottom sheet that will show for each user their first twenty friends and an image that includes their first three friends. Additionally, for each user they wish to get the IDs and usernames of the first twenty friends, as well as the profile images of the first three friends. They are also interested in getting the pagination information for the first twenty friends in order for them to allow the user to ask for more friends of a specific user.
Their query (using aliases) would look something like this:

Now, the problem becomes clear: the custom context will need to hold two different paginationInfo objects. Of course, we can have two of those; however, that is not really scalable because the clients can ask for as many as they would like. Thus, we need to take a different approach to ensure maximum flexibility for our clients.

The Solution, or at Least a Solution

Let’s start by considering where we want to use the input parameters required for the pagination. We know that we will need them as part of the query being sent to the database to avoid over fetching from the database. We also know that the input parameters could be useful in the resolvers of the fields down the query tree.

So, the proposed solution is quite simple: pass the parameters as well. How do we do that? Well, let’s divided it into the two different cases:

Child Field Resolver:

For this case, we can take two approaches. We can simply wrap the returned value of the friends with the input parameters, which promises the continuation of their values to the children based on the query execution. For most cases, however, this will not be necessary. For the most part, we would need only one level down (i.e. in the UserConnectionResolver). For this case, we have a pretty easy solution using DGS as shown here in the example resolver of the pageInfo field:

Data Loaders:

The more interesting case is how we pass the arguments to the data loaders. We accomplish that by passing them in the keys passed to the load method. However, there is more to it than that. Let’s first describe the high-level plan before diving into to the examples. What we want to do is:

  1. Wrap keys & input arguments in a data class
  2. Group by arguments
  3. Run each group as a separate query in parallel

The code looks like:

It is worth noting that although this code may appear quite cumbersome at first sight, after a brief second look, it is not that complicated. We simply group the keys by their arguments and run each such group as if it was a separate query — in parallel, of course.

Why does this make sense? Well, if you look back at the query, each alias of the friends field can only supply one set of arguments , thus making it a very natural way to divide the keys.

Conclusion

Working with DGS custom context can seem to make our development easier, and in some cases it does. Nevertheless, we ought to remember that its scope is the request lifecycle and recognize the pitfalls it could entail when we’re exposing such a flexible API as GraphQL.

Fiverr is hiring. Learn more about us here.

--

--