【问题标题】:GraphQL how to avoid duplicate code between input and output typesGraphQL 如何避免输入和输出类型之间的重复代码
【发布时间】:2018-06-24 22:47:08
【问题描述】:

我是 GraphQL 的新手,但我真的很喜欢它。现在我正在使用接口和联合,我面临着突变的问题。

假设我有这个架构:

interface FoodType {
    id: String
    type: String
    composition: [Ingredient]
  }

  type Pizza implements FoodType {
    id: String
    type: String
    pizzaType: String
    toppings: [String]
    size: String
    composition: [Ingredient]
  }

  type Salad implements FoodType {
    id: String
    type: String
    vegetarian: Boolean
    dressing: Boolean
    composition: [Ingredient]
  }

  type BasicFood implements FoodType {
    id: String
    type: String
    composition: [Ingredient]
  }

  type Ingredient {
      name: String
      qty: Float
      units: String
  }

现在,我想创造新的食物,所以我开始做这样的事情:

type Mutation {
    addPizza(input:Pizza):FoodType
    addSalad(input:Salad):FoodType
    addBasic(input:BasicFood):FoodType
}

这不起作用有两个原因:

  1. 如果我想传递一个对象作为参数,这个对象必须是“输入”类型。但“Pizza”、“Salad”和“BasicFood”只是“类型”。
  2. 输入类型不能实现接口。

所以,我需要像这样修改我以前的架构:

interface FoodType {
    id: String
    type: String
    composition: [Ingredient]
}

type Pizza implements FoodType {
    id: String
    type: String
    pizzaType: String
    toppings: [String]
    size: String
    composition: [Ingredient]
}

type Salad implements FoodType {
    id: String
    type: String
    vegetarian: Boolean
    dressing: Boolean
    composition: [Ingredient]
}

type BasicFood implements FoodType {
    id: String
    type: String
    composition: [Ingredient]
}

type Ingredient {
        name: String
        qty: Float
        units: String
}

type Mutation {
    addPizza(input: PizzaInput): FoodType
    addSalad(input: SaladInput): FoodType
    addBasic(input: BasicInput): FoodType    
}

input PizzaInput {
    type: String
    pizzaType: String
    toppings: [String]
    size: String
    composition: [IngredientInput]
}

input SaladInput {
    type: String
    vegetarian: Boolean
    dressing: Boolean
    composition: [IngredientInput]
}

input BasicFoodInput {
    type: String
    composition: [IngredientInput]
}

input IngredientInput {
        name: String
        qty: Float
        units: String
}

所以,我在这里定义了我的 3 种披萨、沙拉和基本食物的创建方法。 我需要定义 3 种输入类型(每种食物一种) 而且我还需要为成分定义一个新的输入类型。

它会产生很多重复。那样你觉得可以吗?或者有更好的方法来解决这个问题?

谢谢

【问题讨论】:

    标签: interface graphql apollo


    【解决方案1】:

    可以做一些事情。例如,如果您要以编程方式声明您的架构,则可以使用以下方式:

    const getPizzaFields = (isInput = false) => {
      const fields = {
        type: { type: GraphQLString }
        pizzaType: { type: GraphQLString }
        toppings: { type: new GraphQLList(GraphQLString) }
        size: { type: GraphQLString }
        composition: {
          type: isInput ? new GraphQLList(IngredientInput) : new GraphQLList(Ingredient)
        }
      }
      if (!isInput) fields.id = { type: GraphQLString }
      return fields
    }
    
    const Pizza = new GraphQLObjectType({
      name: 'Pizza',
      fields: () => getFields()
    })
    
    const PizzaInput = new GraphQLObjectType({
      name: 'Pizza',
      fields: () => getFields(true)
    })
    

    或者,如果您的对象/输入遵循类似的模式,您甚至可以编写一个函数来从类型生成输入:

    const transformObject = (type) => {
      const input = Object.assign({}, type)
      input.fields.composition.type = new GraphQLList(IngredientInput)
      delete input.fields.id
      return input
    }
    

    或者,在使用 makeExecutableSchema 定义架构时,您可以这样做:

    const commonPizzaFields = `
        type: String
        pizzaType: String
        toppings: [String]
        size: String
    `
    
    const schema = `
      type Pizza {
        id: String
        ${commonPizzaFields}
        composition: [Ingredient]
      }
    
      input PizzaInput {
        ${commonPizzaFields}
        composition: [IngredientInput]
      }
    `
    

    所有这些方法的问题在于,虽然它们在技术上可能会使您的代码更加干燥,但它们也会降低架构的可读性,在我看来,这使得它比重复本身更容易出错。

    同样重要的是要理解,虽然在语法上,Type 和 Input 类型可能看起来相同,但在功能上它们却不同。例如,类型上的字段可能有参数:

    type Pizza {
      toppings(filter: ToppingTypeEnum): [String]
    }
    

    输入类型字段没有参数,因此您将无法在Pizza 类型及其对应的PizzaInput 输入类型中对toppings 字段使用相同的语法。

    就个人而言,我会咬紧牙关,像您已经完成的那样写出类型和输入。我唯一会做的不同是将它们组合在一起(列出您的类型而不是您的输入),以便轻松发现两者之间的任何差异。但你的里程可能非常:)

    【讨论】:

    • commonPizzaFields 就像 fragment 的概念。但是graphqlfragment只用在“graphql客户端”
    【解决方案2】:

    我使用的是 Dgraph,它解决了同样的问题。 看看:https://dgraph.io/docs/graphql/schema/types/#interfaces 基本上,如果使用 dgraph,您只需编写最少的代码,它就会生成休息。 您还可以使用 webstorm 编写:
    # noinspection GraphQLInterfaceImplementation 在文件顶部,它将禁用对接口的检查,然后使用某种转译器来填充使用接口的类型的字段。

    【讨论】:

      最近更新 更多