Тип ответа GraphQL / борьба фрагментов

У меня есть проблемы с написанием API в graphql.

Каждый ответ в моем api должен выглядеть примерно одинаково. Итак, в идеале это был бы тип graphql:

type Response {
  success
  data {
    ... always different
  }
  errors {
    path
    message
  }
}

Но потому что поле данных здесь всегда другое. У каждой мутации / запроса должен быть свой тип ответа (если я правильно понимаю graphql).

Итак, для входа в систему это тип, который я создаю с помощью функции-преобразователя:

type LoginResponse {
  success
  data {
    user
    token
  }
  errors {
    path
    message
  }
}

Теперь в моем интерфейсе я хочу использовать следующий фрагмент, потому что эти свойства всегда присутствуют в каждом ответе.

fragment Response on LoginResponse {
  success
  errors {
    path
    message
  }
}

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

Кто-то, возможно, уже боролся с этим, или есть лучшая практика для этого, я не вижу

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Что такое Apollo Client и зачем он нужен?
Что такое Apollo Client и зачем он нужен?
Apollo Client - это полнофункциональный клиент GraphQL для JavaScript-приложений, который упрощает получение, управление и обновление данных в...
1
0
1 005
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

Общий шаблон, который вы видите в схемах, - это идея интерфейса Node. У вас может быть запрос на выборку узла по идентификатору, например:

type Query {
  node(id: ID!): Node
}

interface Node {
  id: ID!
}

type Foo implements Node {
  id: ID!
  foo: String!
}

type Bar implements Node {
  id: ID!
  bar: Int!
}

Здесь Node может быть либо Foo, либо Bar, поэтому, если бы мы записали фрагмент для Node, он мог бы выглядеть примерно так:

fragment NodeFields on Node {
  id # id is part of the interface itself
  ... on Bar {
    bar # fields specific to Bar
  }
  ... on Foo {
    foo # fields specific to Foo
  }
}

Если у вас нет общих полей, вы можете использовать Union вместо этого с тем же эффектом:

union SomeUnion = Foo | Bar

Итак, чтобы облегчить некоторые повторения в вашем внешнем коде, вы можете сделать каждый из ваших типов Result интерфейсом или, что еще лучше, иметь один тип Result, где data является объединением. К сожалению, ни интерфейсы, ни объединения не работают со скалярами или списками, что усложняет ситуацию, если data должен быть скаляром или списком для некоторых запросов.

В конце концов, однако, вероятно, не рекомендуется изначально структурировать схему таким образом. Есть ряд веских причин избегать такой структуры:

  1. GraphQL уже возвращает результат вашего запроса в виде объекта JSON со свойствами data и errors.
  2. Возврат ошибок внутри GraphQL data потребует дополнительной логики для захвата и форматирования ошибок, в отличие от возможности просто выдать ошибку в любом месте и заставить GraphQL обрабатывать отчеты об ошибках за вас.
  3. Вы не сможете фиксировать ошибки проверки, поэтому вы потенциально можете столкнуться с ошибками в двух местах - внутри массива errors и внутри data.errors. Это также означает, что ваш клиент должен искать ошибки в двух местах, чтобы правильно обрабатывать ошибки.
  4. GraphQL специально разработан, чтобы разрешить частичное разрешение ответа. Это означает, что даже если в некоторых частях ответа возникла ошибка и они не были разрешены, другие могут быть разрешены и возвращены как часть ответа. Это означает, что концепция «успешного» ответа на самом деле не применима в GraphQL. Если вам абсолютно необходимо поле success, было бы гораздо лучше использовать что-то вроде formatResponse, чтобы добавить его к объекту ответа после разрешения запроса.

Это значительно упростит соблюдение соглашений и структурирует вашу схему следующим образом:

type Query {
  login: LoginResponse
}

type LoginResponse {
  token: String
  user: User
}

Фактический ответ по-прежнему будет включать data и errors:

{
  "data": {
    "login": {
      "token": "",
    }
  },
  "errors": []
}

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

Это именно то объяснение, которое я искал. Большое спасибо, чувак! Рефакторинг моих ответов и собираюсь опробовать автоматические ошибки rn. И раньше я видел профсоюзы, но не знал, что вы также можете фрагментировать «на» это.

Bas Kaal 15.12.2018 15:04

? Стоит отметить, что в дополнение к formatResponse вы также можете настроить ApolloServer с функцией formatError для лучшего контроля над тем, как ваши ошибки будут возвращены в ответ.

Daniel Rearden 15.12.2018 15:18

Хорошее видео о том, как обрабатывать ошибки (связанные с этим вопросом): https://thewikihow.com/video_-wRXk_QZ3Ko

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