Как управлять дочерним объектным типом GraphQL, который может быть обнуляем в типе вывода?

Я устанавливаю API-интерфейс GraphQL для nodeJS и экспериментирую с точкой блокировки одного из типов вывода моего ресурса.

Функция представляет собой форму, которая содержит три разных уровня:

  • Уровень 1- formTemplate
  • Уровень 2- formItems (templateId, тип (видео, изображение, вопрос) - 1-N отношение с formTemplate)
  • Уровень 3- formQuestions (0-1 отношение с formItem тогда и только тогда, когда formItems.type равен 'question')

Мой ресурс GraphQL возвращает все шаблоны в базе данных, так что это массив, который для каждого шаблона возвращает все свои элементы, и каждый элемент типа "вопрос" должен возвращать массив, содержащий связанный вопрос.

Моя проблема: я действительно не знаю, как вернуть пустой тип объекта для formItems, где тип отличается от "вопрос" или есть ли лучший подход для такого рода ситуаций

Я пытался взглянуть на директивы GraphQL и встроенные фрагменты, но я думаю, что действительно нужно управлять бэкэндом, потому что это прозрачно для потребителя API.

const formTemplate = new GraphQLObjectType({
  name: 'FormTemplate',
  fields: () => {
    return {
      id: {
        type: new GraphQLNonNull(GraphQLInt)
      },
      authorId: {
        type: new GraphQLNonNull(GraphQLInt)
      },
      name: {
        type: new GraphQLNonNull(GraphQLString)
      },
      items: {
        type: new GraphQLList(formItem),
        resolve: parent => FormItem.findAllByTemplateId(parent.id)
      }
    }
  }
})

const formItem = new GraphQLObjectType({
  name: 'FormItem',
  fields: () => {
    return {
      id: {
        type: new GraphQLNonNull(GraphQLInt)
      },
      templateId: {
        type: new GraphQLNonNull(GraphQLInt)
      },
      type: {
        type: new GraphQLNonNull(GraphQLString)
      },
      question: {
        type: formQuestion,
        resolve: async parent => FormQuestion.findByItemId(parent.id)
      }
    }
  }
})

const formQuestion= new GraphQLObjectType({
  name: 'FormQuestion',
  fields: () => {
    return {
      id: {
        type: new GraphQLNonNull(GraphQLInt)
      },
      itemId: {
        type: new GraphQLNonNull(GraphQLInt)
      },
      type: {
        type: new GraphQLNonNull(GraphQLString)
      },
      label: {
        type: new GraphQLNonNull(GraphQLString)
      }
    }
  }
})

Мой запрос GraphQL:

query {
  getFormTemplates {
    name
    items {
      type
      question {
        label
        type
      }
    }
  }
}

Что я ожидал

{
  "data": {
    "getFormTemplates": [
      {
        "name": "Form 1",
        "items": [
          {
            "type": "question",
            "question": {
              "label": "Question 1",
              "type": "shortText"

          },
          {
            "type": "rawContent"
            "question": {}
          }
        ]
      }
    ]
  }
}

2 ответа

Решение

Спасибо Дэвид за ваш ответ!

Я понял, как решить мою проблему, используя встроенные фрагменты и UnionTypes, которые кажутся наиболее приспособленными для этого варианта использования. Вот код:

const formItemObjectType = new GraphQLUnionType({
  name: 'FormItemObject',
  types: [formItemContent, formItemQuestion],
  resolveType(parent) {
    switch (parent.type) {
      case ('question'): return formItemQuestion
      default: return formItemContent
    }
  }
})

и запрос GraphQL с использованием встроенного фрагмента:

query {
  getFormTemplates {
    name
    items {
      ...on FormItemContent {
        type,
        meta
      }
      ...on FormItemQuestion {
        type,
        meta,
        question {
           label
        }
      }
    }
  }
}

Я бы спроектировал элементы "уровня 2" так, чтобы свойство "тип" соответствовало фактическим типам GraphQL, реализуя общий интерфейс. Кроме того, в общем, я бы разработал схему так, чтобы она содержала реальные ссылки на соседние элементы, а не их идентификаторы.

Так что, если у каждого элемента формы есть связанный шаблон, вы можете сделать его интерфейсом GraphQL:

interface FormItem {
  id: ID!
  template: FormTemplate
}

Тогда вы можете иметь три отдельных типа для ваших трех видов предметов

# Skipping VideoItem
type ImageItem implements FormItem {
  id: ID!
  template: FormTemplate
  src: String!
}
type QuestionItem implements FormItem {
  id: ID!
  template: FormTemplate
  questions: [FormQuestion!]!
}

Другие типы, которые вы описываете:

type FormTemplate {
  id: ID!
  author: Author!
  name: String!
  items: [FormItem!]!
}
type FormQuestion {
  id: ID!
  question: Question
  type: String!
  label: String!
}

Другая хитрость в том, что, поскольку не все элементы формы являются вопросами, вы должны специально указать, что вас интересуют вопросы в вашем запросе, чтобы получить поля для конкретного вопроса. Ваш запрос может выглядеть так

query {
  getFormTemplates {
    name
    items {
      __typename # a GraphQL builtin that gives the type of this object
      ... on Question {
        label
        type
      }
    }
  }
}

... on Question синтаксис является встроенным фрагментом, и вы можете аналогичным образом использовать его для выбора полей, характерных для других видов элементов формы.

Другие вопросы по тегам