GraphQL fetch data at Query level results in redundant/useless request

人走茶凉 提交于 2019-12-14 03:05:51

问题


We are implementing GraphQL service, which stands in front of several backend microservices.

For example, we have a Product and each product has a list of history orders. Our backend server provides two REST APIs, one for product detail data, the other returns the history order list of a product.

Our client app has two pages: one is the product detail page, the other is the history order list of a product.

So, in the product detail page, we can only retrieve the detail data of the product, while in the order list page, we only need the list data.

The GraphQL schema like below:

type ProductOrder {
    createAt: Date!
    userName: String!
    count: Int
}
type Product {
    productId: ID
    name: String
    orders: [ProductOrder!]!
}
Query {
    product(productId: ID): Product
}

and resolvers are like this

const resolvers = {
    Query: {
        product(_, { productId}){
            // fetch detail data from backend API
            return await someService.getProductDetail(productId);
        }
    },
    Product: {
        orders(product){
            // fetch order list from another API
            return await someService.getProductOrders(product.productId);
        }
    }
};

But we find a potential over-request using the above code.

When we request the order list data from the order list page, we have to request the product detail API first, after then we can request the order list API. But we ONLY need the order list data, no product data at all. In this case, we think the product detail request is useless, how can we eliminate this request?

It could be better if we can send only one request to retrieve the order list data.


回答1:


A) Structure your schema differently:

Version 1: Don't make ProductOrder a field on Product

type Query {
  product(productId: ID): Product
  productOrders(productId: ID): [ProductOrder!]
}

type Product {
  productId: ID
  name: String
}

Version 2: Make details a subfield in Product

type Product {
    productId: ID
    details: ProductDetails!
    orders: [ProductOrder!]!
}

type ProductDetails {
  name: String
}

With resolvers:

const resolvers = {
  Query: {
    product: (_, { productId }) => productId,
  },
  Product: {
    id: productId => productId,
    details: productId => someService.getProductDetail(productId),
    orders: productId => someService.getProductOrders(productId),
  },
};

B) Skip fetch if no details are requested

You can use the fourth argument to the resolver to inspect the queried subfields. Ideally you use a library for that. I remember us doing that when our frontend would only request the id field of an object. If so we could simply resolve with { id }.

import { fieldList } from 'graphql-fields-list';

const resolvers = {
  Query: {
    product(_, { productId }, ctx, resolveInfo) {
      const fields = fieldList(resolveInfo);
      if (fields.filter(f => f !== 'orders' || f !== 'id').length === 0) {
        return { productId };
      }
      return someService.getProductDetail(productId);
    },
  },
};

C) Delay fetch until subfield is queried

This is relatively easy to do if you are already using Dataloader. Instead of fetching the details right away in the query resolver you again pass down the id and let each of the details fields fetch the details themselves. This seems counterintuitve but Dataloader will make sure your service is only queried once:

const resolvers = {
  Query: {
    product: (_, { productId }) => productId,
  },
  Product: {
    id: productId => productId,
    // same for all other details fields
    name: (productId, args, ctx) => ctx.ProductDetailsByIdLoader.load(productId)
      .then(product => product.name),
    orders: productId => someService.getProductOrders(productId),
  },
};

If you don't have dataloader you can build a simple proxy:

class ProductProxy {
  constructor(id) {
    this.id = id;
    let cached = null;
    this.getDetails = () => {
      if (cached === null) {
        cached = someService.getProductDetails(productId)
      }
      return cached;
    }
  }
  // args not needed but for you to see how graphql-js works
  productId(args, ctx, resolveInfo) {
    return this.id;
  }
  name(args, ctx, resolveInfo) {
    return this.getDetails().then(details => details.name);
  }
  orders(args, ctx, resolveInfo) {
    return someService.getProductOrders(this.id);
  }
}

const resolvers = {
  Query: {
    product: (_, { productId }) => new ProductProxy(productId),
  },
  // No product resolvers need here
};


来源:https://stackoverflow.com/questions/57988750/graphql-fetch-data-at-query-level-results-in-redundant-useless-request

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!