【问题标题】:What is a correct return type of a GraphQL resolve function?GraphQL 解析函数的正确返回类型是什么?
【发布时间】:2022-02-05 04:57:25
【问题描述】:

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

  1. 我有一个突变 bookAppointment,它返回一个 Appointment 对象
  2. GraphQL 架构表示该对象应返回 4 个属性:iddatespecialistclient
  3. 要遵循 GraphQL 样式,specialistclient 属性应该是字段级解析器
  4. 要获取此对象,我需要将 specialistId 传递给专家字段级解析器,并将 clientId 传递给客户端字段级解析器。
  5. 此时出现问题。
  6. clientspecialist 的字段级解析器期望根突变返回像 clientIdspecialistId 这样的字段。但是 GraphQL 语法和由该语法生成​​的类型不包括这个道具(有意义)。
  7. 如何“扩展”解析器的返回类型及其 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. 创建一个表示解析器“实际”返回类型的接口
...
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 之类的工具会使用应包含在响应中的实际对象生成类型,而不是字段级解析器将使用的字段。 (我错了吗?)

我想知道您是如何解决此类问题的?

【问题讨论】:

  • 这是一个常见问题,我过去解决它的方法是将 clientIdspecialistId 预先作为类型的一部分,这样你就有了“指向”的东西对那些对象。如果您反对在类型中包含这些,则需要在数据库所持有的任何等效项中具有一些底层结构,即具有隐藏的 id 字段来访问这些引用。一般来说,您可以在您的Appointment 界面上拥有这些,或者让clientspecialist 指向约会(这在客户可以有很多约会的情况下很有用)
  • 感谢您的回复。 “这是一个常见问题”我在生成器工具和 graphql doc 本身中都找不到任何提及。将此道具放入 GraphQL 架构中似乎是一种重复,其中通过appointmentId 从数据库中获取clientspecialist 的性能并不是最优的。

标签: graphql apollo-server


【解决方案1】:

我已经对这个问题进行了相当多的调查,并得出了以下结论。

创建一个表示解析器“实际”返回类型的接口

大多数情况下,解析器函数的返回类型(在 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
      }
    }
  }
}

但这并不反映 GraphQL 类型

据我所知没问题

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

是的。我错了。 graphql-generator 有一个配置文件,可用于将默认生成的类型替换为您希望解析器返回的类型。此选项称为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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-05-22
    • 2019-04-12
    • 2018-10-26
    • 1970-01-01
    • 2020-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多