Глубокое понимание GraphQL

задняя часть GraphQL
Глубокое понимание GraphQL

Introducing GraphQL

Graphql – это язык запросов для API, который обеспечивает выполнение существующего запроса данных. Он родился в 2015 году, разработан Facebook, 7 ноября 2018 года, Facebook передает проект Graphql вновь созданному фонду Graphql. В настоящее время Facebook, Twitter, Netflix, PayPal использует Graphql в производственной среде, GitHub API V4 также полностью использует Graphql.

原则

Возвращайте данные точно и предсказуемо

В традиционных интерфейсах RESTful интерфейс должен получать столько полей, сколько передает сервер, поэтому иногда интерфейсу требуется всего несколько полей, а сервер возвращает большой список (особенно давно установленные поля). интерфейс), который не только фильтрует интерфейс для внешнего интерфейса. Поля добавляют сложности и могут вызвать потенциальные проблемы с производительностью. GraphQL позволяет клиенту получать именно те данные, которые ему нужны, без какой-либо избыточности, а процесс фильтрации этих полей в GraphQL не зависит от сервер, но сам Runtime.

export const POSTS = gql`
  query Posts($input: PaginationInput!) {
    posts(input: $input) {
      total
      page
      pageSize
      items {
        _id
        title
        summary
      }
    }
  }
@ObjectType()
export class SMSModel {
  @Field()
  @IsMobilePhone("zh-CN")
  @IsNotEmpty()
  public readonly phoneNumber: string;

  @Field()
  @Length(6)
  @IsNumberString()
  @IsNotEmpty()
  public readonly smsCode: string;
}

запрашивать только один интерфейс

Следующий пример представляет собой классический интерфейс в стиле RESTful. Видно, что для набора добавлений, удалений и изменений необходимо запрашивать разные URL-адреса, что приводит к необходимости нескольких соединений TCP. Хотя HTTP2 обеспечивает мультиплексирование (все соединения под одним и тем же доменное имя) Все выполняется в одном соединении, одно и то же доменное имя должно занимать только одно TCP-соединение и использовать одно соединение для параллельной отправки нескольких запросов и ответов). Но в мобильной среде, где сеть все еще медленная, мы все еще надеемся максимально сократить HTTP-запросы, приложения GraphQL также могут быть достаточно быстрыми.

GET /posts
GET /post/:id
POST /post
PUT /post/:id
DELETE /post/:id
{
  operationName: "Posts",
  query: "...",
  variables: {
    input: {
      page: 1,
      pageSize: 10,
    },
  },
}

SDL(schema definition languages)

Type Language

GraphQL не зависит ни от какого языка программирования, потому что мы не зависим от синтаксиса и синтаксиса какого-то конкретного языка, у него есть свой набор схем.

type Language {
  code: String!
  name: String!
  native: String!
}

type Location {
  geoname_id: Float!
  capital: String!
  languages: [Language!]!
  country_flag: String!
  country_flag_emoji: String!
  country_flag_emoji_unicode: String!
  calling_code: String!
  is_eu: Boolean!
  created_at: DateTime!
}
  1. LanguageПредставители графики对象类型, обычно используемый для согласования ответа серверной части.
  2. code, name, nativeдаLanguageтип字段, что означает, что вы запрашиваетеLanguageМожно искать только одно или несколько из этих трех полей, а любое другое поле будет ошибкой.
  3. code: String!значитcodeизскалярдаString, восклицательный знак означает, что поле не пусто, и если бэкэнд вернет, что поле пусто, также будет сообщено об ошибке.
  4. languages: [Language!]!значитlanguagesТипLanguage 数组, и массив не может быть пустым.
type Query {
  getPosts(input: PaginationInput!): PostModel!
}

type Mutation {
  createPost(input: CreatePostInput!): PostItemModel!
}

input CreatePostInput {
  posterUrl: String!
  title: String!
  summary: String!
  content: String!
  tags: [String!]!
  lastModifiedDate: String!
  isPublic: Boolean
}

QueryиMutationдва встроенных специальных типа, вы можете понять их как спокойныеGETиPOST, первый используется для запросов, второй — для добавлений, удалений и модификаций.QueryМожно вносить дополнения, удаления и изменения, но для семантики рекомендуется использовать их отдельно.

Первый оператор определяет запрос,getPostможно сравнить с путем в интерфейсе RESTful; в то время какinputЗатем вы можете провести аналогию с параметрами, размещенными в теле, которыеCreatePostInputтип и должен быть передан,inputОпределения типа или тип объекта изменяющиеся параметры передачи; этот запрос возвращаетPostModelТип данных, и данные должны быть ненулевыми.Второй оператор определяет изменение с той же семантикой.

Scalar

"Скаляр" можно понимать как основной тип полей в GraphQL. По умолчанию существует пять типов Int, Float, String, Boolean и ID. Иногда вам нужно расширить скаляр, который подходит для вашего бизнеса, и каждый скаляр должен быть быть реализованнымparseValue, serialize, parseLiteralТри метода заключаются в следующемDateScalar.

import { Scalar, CustomScalar } from "@nestjs/graphql";
import { Kind, ValueNode } from "graphql";

@Scalar("Date")
export class DateScalar implements CustomScalar<number, Date> {
  description = "Date custom scalar type";

  parseValue(value: number): Date {
    return new Date(value); // value from the client
  }

  serialize(value: Date): number {
    return value.getTime(); // value sent to the client
  }

  parseLiteral(ast: ValueNode): Date {
    if (ast.kind === Kind.INT) {
      return new Date(ast.value);
    }
    return null;
  }
}

Скаляр предназначен для более точного определения типа поля, но писать новое действительно хлопотно, к счастьюgraphql-scalarsПредустановлено около 50 скаляров, таких как PositiveInt, NegativeInt, DateTime, Date, EmailAddress, HexColorCode и т. д.

Enum

Тип перечисления — это особый тип скаляра, ограниченный специальным набором необязательных значений, что позволяет:

  1. Убедитесь, что любой параметр этого типа является одним из необязательных значений.
  2. При общении с системой типов поле всегда является одним из конечного набора значений.
enum PostStatus {
  DRAFT
  PUBLISH
}

Interfaces

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

interface Common {
  status_msg: String!
  status_code: Int!
}

type User implements Common {
  id: ID!
  name: String!
  email: String!
  status_msg: String!
  status_code: Int!
}

код сначала

В реальной разработке мы можем создать GraphQL SDL, написав на родном языке GraphQL, как указано выше, конечно, мы также можем сгенерировать его в первую очередь с помощью кода, то есть с помощью декоратора TypeScript.Следующий код, в дополнение к определению типа поле, напримерposterUrlТипStringСкалярный и непустой; также может «уносить частные блага», такие как ограниченияposterUrlдаurlСтрока формата, которая ограничивает тип данных более мелкозернистой манерой.

@InputType()
export class CreatePostInput {
  @Field({ nullable: false })
  @IsString()
  @IsUrl({ protocols: ["https"], require_protocol: true })
  @IsNotEmpty()
  public readonly posterUrl: string;

  @Field({ nullable: false })
  @IsString()
  @MinLength(1)
  @MaxLength(20)
  @IsNotEmpty()
  public readonly title: string;

  @Field({ nullable: false })
  @IsString()
  @IsNotEmpty()
  public readonly summary: string;

  @Field({ nullable: false })
  @IsString()
  @IsNotEmpty()
  public readonly content: string;

  @Field(() => [String], { nullable: false })
  @IsArray()
  @IsString({ each: true })
  @ArrayNotEmpty()
  @ArrayUnique()
  @IsNotEmpty()
  public readonly tags: string[];

  @Field({ nullable: false })
  @IsString()
  @IsNotEmpty()
  public readonly lastModifiedDate: string;

  @Field({ nullable: true })
  public readonly isPublic?: boolean;
}

Следующий код представляет собой синтаксический анализатор GraphQL, который также создает запросы и мутации с помощью аннотаций:

  • @Query(() => PostItemModel)представляет возвращаемое значениеPostItemModelтип;
  • getPostByIdопределить имя этого запроса;
  • @Args({ name: "id", type: () => ID })Используется для определения параметров, мне нужно передать идентификатор поля, его скаляр - идентификатор
@Resolver()
export class PostsResolver {
  constructor(private readonly postsService: PostsService) {
    this.postsService = postsService;
  }

  @Query(() => PostItemModel)
  public async getPostById(@Args({ name: "id", type: () => ID }) id: string) {
    return this.postsService.findOneById(id); // 处理 SQL
  }

  @Mutation(() => PostItemModel)
  @UseGuards(GqlAuthGuard)
  public async createPost(@Args("input") input: CreatePostInput) {
    return this.postsService.create(input); // 处理 SQL
  }
}

внешний интерфейс

GrapqhQL

Суть графаql на переднем конце - это интерфейс с вами, напримерhttps://api.example.com/graphqlНа сервер отправляется запрос POST, а тело запроса показано на рисунке выше, но для лучшего взаимодействия с синтаксисом GrapqhQL во внешнем интерфейсе появилось несколько хороших библиотек, таких как собственная библиотека Facebook.relay, relay претерпел две основные итерации, и текущий официальный сайт Facebook использует последнее поколение, называемое relay morden.

facebook

Хотя relay является проектом с открытым исходным кодом, он больше предназначен для внутренних бизнес-сервисов Facebook, поэтому его трудно использовать посторонним.Apollo, который поддерживает внешний фреймворк React на основе Hooks, а также Vue, Angular, Android и iOS, а также предоставляет серверный фреймворк Appolo на основе Node.js.

apollo

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

Второй фрагмент кода, запрошенное изменениеcreatePost, его параметрыinputдаCreatePostInputВведите и должно быть передано.Поскольку мы используем фрагмент, нам нужно внедрить соответствующий фрагмент.

Третий фрагмент кода должен фактически инициировать запрос в jsx.Через ловушки можно легко обрабатывать тело запроса, загрузку, возвращаемое значение, обработку ошибок и т. д....

const POST_FRAGMENT = gql`
  fragment PostFragment on PostItemModel {
    _id
    posterUrl
    title
    summary
    content
    tags
    lastModifiedDate
    like
    pv
    isPublic
    createdAt
    updatedAt
  }
`;

export const CREATE_ONE_POST = gql`
  mutation CreatePost($input: CreatePostInput!) {
    createPost(input: $input) {
      ...PostFragment
    }
  }
  ${POST_FRAGMENT}
`;

const [createPost, { loading }] = useMutation<
  CreatePostMutation,
  CreatePostVars
>(CREATE_ONE_POST, {
  onCompleted(data) {
    const newPost = data.createPost;
    enqueueSnackbar("Create success!", { variant: "success" });
  },
  onError() {},
});

Introspection

В реальной разработке мы определим ряд запросов, мутаций, ввода, типа, перечисления, скаляра, интерфейса на бэкэнд. GraphQL поддерживает мощную систему самоанализа. Через систему самоанализа мы можем проверить набор схем, разработанных на Другая функция системы самоанализа — помощь в разработке инструментов GraphQL. Путем запроса внутренней схемы можно построить мощную IDE. Следующий код может запрашиватьPostItemModelВся информация этого типа.

{
  __type(name: "PostItemModel") {
    name
    fields {
      name
      type {
        name
        kind
      }
    }
  }
}
{
  "data": {
    "__type": {
      "name": "PostItemModel",
      "fields": [
        {
          "name": "_id",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "posterUrl",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "title",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "summary",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "content",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "tags",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "lastModifiedDate",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "like",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "pv",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "isPublic",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "createdAt",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "updatedAt",
          "type": {
            "name": null,
            "kind": "NON_NULL"
          }
        },
        {
          "name": "prev",
          "type": {
            "name": "PostItemModel",
            "kind": "OBJECT"
          }
        },
        {
          "name": "next",
          "type": {
            "name": "PostItemModel",
            "kind": "OBJECT"
          }
        }
      ]
    }
  }
}

Безопасность

Производственная среда Закрытьdebug

Если вы включитеdebugрежим, который отображает неверную информацию о стеке при ошибках.

debug 模式会展示堆栈信息

Производственная среда отключаетсяplayground

playgroundДолжен использоваться как вспомогательный инструмент самопроверки, не должен выставляться в сети.

Производственная среда отключаетсяintrospection

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

Контроль многослойного расследования глубины

Следующее может привести к дорогим запросам, и ключ вызван коллапсом заднего конца. Может быть использованgraphql-depth-limitчтобы указать наиболее запрашиваемый уровень.

query {
  author(id: 42) {
    posts {
      author {
        posts {
          author {
            posts {
              author {
                # and so on...
              }
            }
          }
        }
      }
    }
  }
}

Контролируйте количество данных подкачки

Таким образом, за один раз будет получено не более 100 000 единиц данных, что, очевидно, вызовет проблемы с производительностью.graphql-input-numberОграничьте максимальное значение чисел в распознавателе.

query {
  authors(first: 1000) {
    name
    posts(last: 100) {
      title
      content
    }
  }
}

Конечно, если вы используетеclass-validator, также может быть ограничен следующими способами.

@InputType()
export class SomeNumberInput {
  @IsInt()
  @Min(1)
  @Max(10)
  public readonly pageSize: number;
}

Ссылаться на