GraphQL 如何避免输入和输出类型之间的重复代码
GraphQL how to avoid duplicate code between input and output types
我是 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
}
由于两个原因,这没有用:
- 如果我想传递一个对象作为参数,这个对象必须是 "input" 类型。但是 "Pizza"、"Salad" 和 "BasicFood" 只是 "type"。
- 输入类型不能实现接口。
所以,我需要像这样修改我以前的模式:
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 种输入类型(每种食物一种)
我还需要为 Ingredients 定义一个新的输入类型。
它造成了很多重复。那样你觉得可以吗?或者有更好的方法来处理这个问题?
谢谢
您可以做一些事情。例如,如果您要以编程方式声明您的模式,您可以这样处理:
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)
})
或者,如果您的 objects/inputs 遵循类似的模式,您甚至可以编写一个函数来从以下类型生成输入:
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
字段使用相同的语法输入.
就我个人而言,我会硬着头皮写出类型和输入,就像您已经完成的那样。我唯一会做的不同是将它们组合在一起(列出您的类型而不是您的输入),以便很容易发现两者之间的任何差异。但是你的里程可能会很:)
我用的是 Dgraph,它解决了同样的问题。
看看:https://dgraph.io/docs/graphql/schema/types/#interfaces
基本上,如果使用 dgraph,您只需编写最少量的代码,它就会生成 rest。
还可以使用 webstorm 编写:
# noinspection GraphQLInterfaceImplementation
在文件顶部,它将禁用对接口的检查,然后使用某种类型的转换器来填充使用接口的类型上的字段。
我是 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
}
由于两个原因,这没有用:
- 如果我想传递一个对象作为参数,这个对象必须是 "input" 类型。但是 "Pizza"、"Salad" 和 "BasicFood" 只是 "type"。
- 输入类型不能实现接口。
所以,我需要像这样修改我以前的模式:
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 种输入类型(每种食物一种) 我还需要为 Ingredients 定义一个新的输入类型。
它造成了很多重复。那样你觉得可以吗?或者有更好的方法来处理这个问题?
谢谢
您可以做一些事情。例如,如果您要以编程方式声明您的模式,您可以这样处理:
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)
})
或者,如果您的 objects/inputs 遵循类似的模式,您甚至可以编写一个函数来从以下类型生成输入:
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
字段使用相同的语法输入.
就我个人而言,我会硬着头皮写出类型和输入,就像您已经完成的那样。我唯一会做的不同是将它们组合在一起(列出您的类型而不是您的输入),以便很容易发现两者之间的任何差异。但是你的里程可能会很:)
我用的是 Dgraph,它解决了同样的问题。
看看:https://dgraph.io/docs/graphql/schema/types/#interfaces
基本上,如果使用 dgraph,您只需编写最少量的代码,它就会生成 rest。
还可以使用 webstorm 编写:
# noinspection GraphQLInterfaceImplementation
在文件顶部,它将禁用对接口的检查,然后使用某种类型的转换器来填充使用接口的类型上的字段。