Questions to ask when choosing a Chiropractor

Chiropractors are trained health care professionals who specialize in the diagnosis and treatment of neuromuscular disorders. They treat the joint pains and educate the patients on improving the same…

Smartphone

独家优惠奖金 100% 高达 1 BTC + 180 免费旋转




From GraphQL queries to resolvers and back

Wondering how GraphQL resolvers should share data, and how to reuse them multiple times in your schema? You’ve come to the right place…

For the last couple of weeks, we have been setting up a GraphQL API for one of our customers. While doing that, two major elements took us a while to figure out, and looking around on the web, I could not find this information stated plainly so, here is my attempt to document this for anyone stumbling on the same issues.

To set the table for this problem, let me introduce an example. Let’s say we want to expose the following schema:

To calculate the order total, we need to sum every item and multiple by the item quantity. To do that, we need to fetch the items from the database. Our resolver setup looks like this:

And here comes the first problem we encountered. The total and items resolver both need to fetch the items, so if both are independent, they will both call the database to retrieve the items? So how do we share the information between the two to avoid performing the same query twice? The answer is, we don’t!

The correct answer to this question is that we need a caching layer. Each resolver needs to ask for the data it needs. The first one to ask will trigger the fetching of the required data (rest call, database query, …), while the second one requesting the same data will get it for free because the DataSource will act as a caching layer, at the request level. It’s important to understand that the cache is per-request, not global.

Obviously, if you are not using Apollo, you can still use the same idea, but you will have to implement the same request based caching by yourself.

Let build on our previous example and add another query to our schema

We also need additional resolvers

Let’s try one query on this API and see how it works.

The first thing we need to understand is that for each field in a query, the GraphQL engine will call a resolver, using a breadth-first exploration of the query. If a field doesn’t have a declared resolver, GraphQL will apply a default resolver.

The second thing to understand is that a resolver should never eager-fetch data. It should return an object where the data cost the same to retrieve. Other resolvers are used to retrieve associated data if asked in the query.

Here is the flow of resolvers matching the query elements

#1 — The first field declared in the query is getOrder.
resolvers.getOrder is called. It fetches the data and returns

Notice that it doesn’t return the order’s total or the items, because to do either, it would need to retrieve the items, which is more costly than retrieving the description and date and we don’t know yet if either field is part of the query. We don’t know if either the Date or Description are required, but since it doesn’t cost more to retrieve them, we do it in one fetch.

#2— The getOrder resolver returns an Ordertype, so the engine will select resolvers.Order. The next field in the query is date.
resolvers.Order doesn’t declare a date resolver, so the default resolver applies. The default resolver receives the object returned by the getOrder resolver (step 1). It tries to find a property in the object with the name of the field in the query, which is date. Since this field exists, it returns the value 2019–05–04.

Our total resolver needs to fetch the items to calculate the total, so it calls a dataSource and retrieves the items. It then proceeds to calculate the total by summing all the items’ prices times quantity and return a float.

#4— Our query is done and the resulting object builds from all the leaf-resolver of the breadth-first iteration the GraphQL engine did on the query tree.

Now let’s trace a second query to see the reuse at work. Here is the query:

Here is the flow of resolvers matching the query elements

#1 — The first field declared in the query is getUser.
resolvers.getUseris called. It fetches the data and returns

Notice that again, it doesn’t return a list of orders, because to do so it would need to retrieve them, which is more costly than retrieving the user’s name and we don’t know yet if this is part of the query. Notice also that we return the name property, but it doesn’t appear in our query. The default resolver will not be called for the name property, because it’s not part of the query, so this value will be ignored.

#2— The getUser resolver returns a User type, so the engine will select resolvers.User. The next field in the query is orders. resolvers.User.orders is called to resolve. This resolver receives our User object from step 1. It uses the idproperty to retrieve a list of orders and returns

Notice again that we return a description and date for each order, but our query only includes the description. The default resolver will not be called for the date field, so it will be ignored. As for the description field, let’s continue to step 3!

#3 — For each order, we need the description. Since our previous resolver returns a list of type Order, the engine uses resolvers.Order.

resolvers.Order doesn’t declare a description resolver, so the default resolver is used to use the description field from the object returned at step 2.

#4— The next field is total.
resolvers.Order.total exists, so it’s called for each order from step 2 and a total is computed.

#5 — Our final payload is built from the returned values or each leaf-resolvers.

As we can see, the Order resolver from our first query is reused in our second query.

We could go further and add the possibility to get the User from an Order. To do so, we only have to change our schema to add a user property to the Order type and then add a property user to the Order resolver.

Update:

I later found a great article from the Paypal team: GraphQL Resolvers: Best Practices. I like their last example since it’s really easy to understand and test, but what you may not realize is that to implement it, you would need to declare a resolver for each fields and never use the default resolver. This would result in a lot of code. I like the approach I describe here better, where the parent resolver is used to resolve the object basic field, and the type resolver is used for relations.

Add a comment

Related posts:

MAYBE SOMEDAY

Satu lagi review untuk buku karya Colleen Hoover, penulis novel romantis favoritku. Meskipun baru sekarang bisa baca cerita lama ini, tapi aku akan tetap memberi review yang terbaik. Dua tokoh utama…

El trigo mejora en Argentina luego de las lluvias

Gracias a las precipitaciones, la proyección del rinde mejoró con respecto la semana pasada. Sin embargo, en el sureste de Córdoba, noroeste de Bs As y sur de Santa Fe todavía hace falta más agua. De…