什么是正确的 return 类型的 GraphQL 解析函数?

What is a correct return type of a GraphQL resolve function?

我遇到了一个我自己无法解决的问题。让我们一步一步来指出问题。

  1. 我有一个突变 bookAppointment,它 return 是一个 Appointment 对象
  2. GraphQL 模式表示该对象应该 return 4 个属性:iddatespecialistclient.
  3. 为了遵循 GraphQL 风格,specialistclient 属性应该是字段级解析器
  4. 要获取此对象,我需要将 specialistId 传递给专家字段级解析器,并将 clientId 传递给客户端字段级解析器。
  5. 此时问题出现了
  6. clientspecialist 的字段级解析器期望根突变 returns 字段,如 clientIdspecialistId。但是由该语法生成​​的 GraphQL 语法和类型不包含此道具(有意义)。
  7. 如何“扩展”解析器的 return 类型及其 interface BookAppointmentPayload 让我和 TypeScript 开心?

这是我的 GraphQL 架构

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

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

type Appointment {
  id: ID!
  date: Date!
  client: Client!
  specialist: Specialist!
}

input BookAppointmentInput {
  date: Date!
  userId: ID!
  specialistId: ID!
}

type BookAppointmentPayload {
  appointment: Appointment!
}

type Mutation {
  bookAppointment(input: BookAppointmentInput!): BookAppointmentPayload!
}

这是 GraphQL 模式的 TypeScript 表示


interface Client {
  id: string
  name: string
}

interface Specialist {
  id: string
  name: string
}

interface Appointment {
  id: string
  date: Date
  client: Client
  specialist: Specialist
}

interface BookAppointmentPayload {
  appointment: Appointment
}

这里我定义了我的解析器对象

const resolvers = {
  ...
  Mutation: {
    bookAppointment: (parent, args, context, info): BookAppointmentPayload => {
      return {
        appointment: {
          id: '1',
          date: new Date(),
          clientId: '1', // This prop doesn't exist in the TypeScript interface of Appointment, but is required for the field-level resolver of a `client` prop
          specialistId: '1' // This prop doesn't exist int he TypeScript interface of Appointment, but is required for the field-level resolver of a `specialist` prop
        }
      }
    }
  },
  Appointment: {
    client: (parent, args, context, info) => {
      // I need a clientId (e.g. args.clientId) to fetch the client object from the database

      return {
        id: '1',
        name: 'Jhon'
      }
    },
    specialist: (parent, args, context, info) => {
      // I need a specialistId (e.g. args.specialistId) to fetch the specialist object from the database

      return {
        id: '1',
        name: 'Jane'
      }
    }
  }
}

我想到的解决方案:

  1. 创建一个表示解析器“实际”return类型的接口
...
interface Apppointment {
  id: string
  date: Date
  clientId: string // instead of `client: Client`
  specialistId: string // instead of `specialist: Specialist`
}

interface BookAppointmentPayload {
  appointment: Appointment
}
...

但这并不反映 GraphQL 类型。此外,graphql-generator 等工具生成的类型包含应包含在响应中的实际对象,而不是字段级解析器将使用的字段。 (我错了吗?)

我想知道你是如何解决这个问题的?

我已经对这个问题进行了大量调查,并得出以下结论。

Create an interface which represent "actual" return type of the resolver

大多数时候 return 类型的解析器函数(在 JavaScript 中)与 GraphQL SDL

中声明的类型不匹配

例如,

# GraphQL SDL

type Appointment {
  id: String!
  client: User!
  specialist: Specialist!
}

type BookAppointmentInput { ... }

type BookAppointmentPayload {
  appointment: Appointment!
}

type Mutation {
  bookAppointment: (input: BookAppointmentInput!): BookAppointmentPayload!
}


interface AppointmentDatabaseEntity {
  id: string
  clientId: string // In GraphQL-world this prop is an object, but not in JS. Use this prop in field-level resolver to fetch entire object
  specialistId: string // In GraphQL-world this prop is an object, but not in JS. Use this prop in field-level resolver to fetch entire object
}

interface BookAppointmentPayload {
  appointment: AppointmentDatabaseEntity // The return type SHOULDN'T be equal to the GraphQL type (Appointment) 
}

const resolvers = {
  Mutatiuon: {
    bookAppointment: (parent, args, context, info) => {
      const appointment = { id: '1', specialistId: '1', clientId: '1' }

      return {
        id: appointment.id,
        specialistId: appointment.specialistId, // Pass this prop to the child resolvers to fetch entire object
        clientId: appointment.clientId // Pass this prop to the child resolvers to fetch entire object
      }
    }
  },
  Appointment: {
    client: (parent: AppointmentDatabaseEntity, args, context, info) => {
      const client = database.getClient(parent.clientId) // Fetching entire object by the property from the parent object
 
      return {
        id: client.id,
        name: client.name,
        email: client.email
      }
    },
    specialist: (parent: AppointmentDatabaseEntity, args, context, info) => {
      const specialist = database.getSpecialist(parent.specialistId) // Fetching entire object by the property from the parent object
 
      return {
        id: specialist.id,
        name: specialist.name,
        email: specialist.email
      }
    }
  }
}

But this doesn't reflect the GraphQL type

据我了解还可以

Also tools like graphql-generator generates the type with actual objects that should be included in the response, not the fields that are going to be used by field-level resolvers. (Am I wrong?)

是的。我错了。 graphql-generator 有一个配置文件,可用于将默认生成的类型替换为您希望解析器 return 生成的类型。此选项称为 mappers

plugins
  config:
    mappers:
      User: ./my-models#UserDbObject # User is GraphQL object, which will be replaced with UserDbObject
      Book: ./my-modelsBook # Same rule goes here

我不想详细介绍如何配置和使用它,但您可以查看帮助我理解它的链接

如果您不同意我的结论,或者您对如何处理它有更好的理解,请随时发表评论