如何为附加字段组织 GraphQL 解析器

How to organize GraphQL resolver for additional fields

假设我有一个简单的 GraphQL 类型供用户使用:

type User {
 id: ID!
 name: String!
}

Query {
  user(id:ID!)
}

和解析器

user = (_, {id}, {api})=> api.getUser(id)

现在我已经向 User 添加了一个名为 friends 的新字段,并为 User.friends 字段添加了一个新的解析器。

friends = ({id}, _, {api})=> api.getFriends(id)

所以现在我想知道当我们进行这样的查询时,我怎样才能阻止对 api.getUser 的调用但只调用 api.getFriends.

query {
  user(id){
    friends {
      name
    }
  }
}

我的理解是,为 Query 类型中的 user 字段定义解析器,它将始终首先调用此解析器,然后再调用 [=14= 中字段的所有解析器]类型。

这是一个常见问题,例如有以下解决方案:https://github.com/gajus/graphql-lazyloader 查看项目的 README 以获取问题的结构化描述。

或者,您可以实现自己的 class,其中包含利用 GraphQL.js 实现默认解析器的方式的缓存值:

class User {
  constructor(id) {
    this.id = id;
  }

  getInstance({ api }) {
    if (!this.instance) {
      this.instance = api.getUser(this.id);
    }
    return this.instance;
  }

  // notice how id is already a property of this class

  name(args, ctx) {
    return this.getInstance(ctx).then(instance => instance.name);
  }

  // do the same for other fields, user will only be fetched once.

  friends(args, { api }) {
    return api.getFriends(this.id);
  }
}

const resolvers = {
  Query: {
    user: (args) => new User(args.id),
  }
}

如果您使用数据加载器,由于数据加载器中的缓存,您甚至可以用更少的代码完成此操作:

// You probably have this function already somewhere in your apollo server creation
function createContext({ api }) {
  return {
    api,
    loaders: {
      user: new Dataloader((ids) => ids.map(id => api.getUser(id))),
    },
  }
}

const resolvers = {
  Query: {
    user: (parent, args) => ({ id: args.id }),
  },
  User: {
    name: ({ id }, args, { loaders }) =>
      loaders.user.load(id).then(user => user.name),
    otherProp: ({ id }, args, { loaders }) =>
      loaders.user.load(id).then(user => user.otherProp),
    friends: ({ id }, args, { api })=> api.getFriends(id),
  }
}

Dataloader 即使被调用两次,也只会到达 API 一次。一个额外的好处是,它将缓存值。理想情况下,您甚至可以在 API 中提供批量加载功能,使加载程序更加高效。

请注意,user.fields.name 现在会向 API 的每个朋友发出呼叫。为避免这种情况,您可以检查 属性 是否存在:

    name: (parent, args, { loaders }) =>
      parent.name ?? loaders.user.load(parent.id).then(user => user.name),