Введение в GraphQL

1. Обзор

GraphQL - язык запросов, созданный Facebook с целью создания клиентских приложений на основе интуитивно понятного и гибкого синтаксиса для описания требований к данным и взаимодействий.

Одна из основных проблем с традиционными вызовами REST - неспособность клиента запросить настроенный (ограниченный или расширенный) набор данных. В большинстве случаев, когда клиент запрашивает информацию с сервера, он либо получает все, либо ни одно из полей.

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

При построении сервера GraphQL необходим только один URL-адрес для выборки и изменения всех данных. Таким образом, клиент может запросить набор данных, отправив на сервер строку запроса, описывающую то, что он хочет.

2. Базовая номенклатура GraphQL

Давайте посмотрим на базовую терминологию GraphQL.

  • Запрос: это операция только для чтения, запрошенная серверу GraphQL

  • Мутация: - операция чтения-записи, запрошенная на сервере GraphQL

  • Resolver: В GraphQL Resolver отвечает за отображение

операция и код, работающий на бэкэнде, который отвечает за обработать запрос. Это аналог бэкэнда MVC в RESTFul приложение Тип: ** Type определяет форму данных ответа, которые могут быть

возвращается с сервера GraphQL, включая поля, являющиеся ребрами другие Types Input: ** как Type, , но определяет форму входных данных, которая

отправлено на сервер GraphQL Scalar: ** это примитив Type , такой как String , Int , Boolean ,

Float и т. Д. Интерфейс: ** Интерфейс будет хранить имена полей и их

аргументы, поэтому объекты GraphQL могут наследоваться от него, гарантируя использование конкретные поля Схема: ** В GraphQL Схема управляет запросами и мутациями,

определение того, что разрешено выполнять на сервере GraphQL

2.1. Загрузка схемы

Существует два способа загрузки схемы на сервер GraphQL:

, с помощью языка определения интерфейса GraphQL (IDL)

, используя один из поддерживаемых языков программирования

Давайте продемонстрируем пример с использованием IDL:

type User {
    firstName: String
}

Теперь пример определения схемы с использованием кода Java:

GraphQLObjectType userType = newObject()
  .name("User")
  .field(newFieldDefinition()
    .name("firstName")
    .type(GraphQLString))
  .build();

3. Язык определения интерфейса

Язык определения интерфейса (IDL) или язык определения схемы (SDL) - наиболее краткий способ указать схему GraphQL. Синтаксис четко определен и будет принят в официальной спецификации GraphQL.

Например, давайте создадим схему GraphQL для пользователя/электронной почты можно указать так:

schema {
    query: QueryType
}

enum Gender {
    MALE
    FEMALE
}

type User {
    id: String!
    firstName: String!
    lastName: String!
    createdAt: DateTime!
    age: Int! @default(value: 0)
    gender:[Gender]!
    emails:[Email!]! @relation(name: "Emails")
}

type Email {
    id: String!
    email: String!
    default: Int! @default(value: 0)
    user: User @relation(name: "Emails")
}

4. GraphQL-Java

GraphQL-java - это реализация, основанная на спецификации и ссылочной реализации JavaScript. Обратите внимание, что для правильной работы требуется как минимум Java 8.

4.1. GraphQL-java Аннотации

GraphQL также позволяет использовать Java annotations для генерации определения схемы без всего стандартного кода, созданного с использованием традиционного подхода IDL.

4.2. зависимости

Чтобы создать наш пример, давайте сначала начнем импортировать необходимую зависимость, основанную на https://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22com.graphql-java%22%20AND%20a Модуль% 3A% 22graphql-java-annotations% 22[Graphql-java-annotations]:

<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphql-java-annotations</artifactId>
    <version>3.0.3</version>
</dependency>

Мы также реализуем библиотеку HTTP, чтобы упростить настройку в нашем приложении. Мы будем использовать Ratpack (хотя это также может быть реализовано с помощью Vert.x, Spark, Dropwizard, Spring Boot и т. д.).

Давайте также импортируем зависимость Ratpack:

<dependency>
    <groupId>io.ratpack</groupId>
    <artifactId>ratpack-core</artifactId>
    <version>1.4.6</version>
</dependency>

4.3. Реализация

Давайте создадим наш пример: простой API, который предоставляет пользователям «CRUDL» (создание, получение, обновление, удаление и список). Во-первых, давайте создадим наш User POJO:

@GraphQLName("user")
public class User {

    @GraphQLField
    private Long id;

    @GraphQLField
    private String name;

    @GraphQLField
    private String email;

   //getters, setters, constructors, and helper methods omitted
}

В этом POJO мы можем видеть аннотацию @ GraphQLName («user») , как указание на то, что этот класс отображается в GraphQL вместе с каждым полем, помеченным @GraphQLField.

Далее мы создадим класс UserHandler . Этот класс наследует от выбранной библиотеки коннекторов HTTP (в нашем случае Ratpack) метод-обработчик, который будет управлять и вызывать функцию GraphQL Resolver . Таким образом, перенаправление запроса (полезных нагрузок JSON) на правильную операцию запроса или мутации:

@Override
public void handle(Context context) throws Exception {
    context.parse(Map.class)
      .then(payload -> {
          Map<String, Object> parameters = (Map<String, Object>)
            payload.get("parameters");
          ExecutionResult executionResult = graphql
            .execute(payload.get(SchemaUtils.QUERY)
              .toString(), null, this, parameters);
          Map<String, Object> result = new LinkedHashMap<>();
          if (executionResult.getErrors().isEmpty()) {
              result.put(SchemaUtils.DATA, executionResult.getData());
          } else {
              result.put(SchemaUtils.ERRORS, executionResult.getErrors());
              LOGGER.warning("Errors: " + executionResult.getErrors());
          }
          context.render(json(result));
      });
}

Теперь класс, который будет поддерживать операции запроса, т.е. UserQuery. Как уже упоминалось, все методы, которые получают данные с сервера на клиент, управляются этим классом:

@GraphQLName("query")
public class UserQuery {

    @GraphQLField
    public static User retrieveUser(
     DataFetchingEnvironment env,
      @NotNull @GraphQLName("id") String id) {
       //return user
    }

    @GraphQLField
    public static List<User> listUsers(DataFetchingEnvironment env) {
       //return list of users
    }
}

Аналогично UserQuery, теперь мы создаем UserMutation, который будет управлять всеми операциями, которые намереваются изменить некоторые данные, хранящиеся на стороне сервера:

@GraphQLName("mutation")
public class UserMutation {

    @GraphQLField
    public static User createUser(
      DataFetchingEnvironment env,
      @NotNull @GraphQLName("name") String name,
      @NotNull @GraphQLName("email") String email) {
     //create user information
    }
}

Стоит обратить внимание на аннотации в классах UserQuery и UserMutation : @ GraphQLName («query») и @ GraphQLName («mutation») . Эти аннотации используются для определения операций запроса и мутации соответственно.

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

  • Для операции CREATE:

{
    "query": "mutation($name: String! $email: String!){
       createUser (name: $name email: $email) { id name email age } }",
    "parameters": {
        "name": "John",
        "email": "[email protected]"
     }
}

Как ответ от сервера на эту операцию:

{
    "data": {
        "createUser": {
            "id": 1,
            "name": "John",
            "email": "[email protected]"
        }
    }
}
  • Для операции ПОЛУЧИТЬ:

{
    "query": "query($id: String!){ retrieveUser (id: $id) {name email} }",
    "parameters": {
        "id": 1
    }
}

Как ответ от сервера на эту операцию:

{
    "data": {
        "retrieveUser": {
            "name": "John",
            "email": "[email protected]"
        }
    }
}

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

{
    "query": "query($id: String!){ retrieveUser (id: $id) {email} }",
    "parameters": {
        "id": 1
    }
}

Таким образом, возвращаемая информация с сервера GraphQL будет возвращать только запрошенные данные:

{
    "data": {
        "retrieveUser": {
            "email": "[email protected]"
        }
    }
}

5. Заключение

GraphQL - это простой и довольно привлекательный способ минимизации сложности между клиентом и сервером в качестве альтернативного подхода к API REST.

Как всегда, пример доступен в нашем GitHub хранилище .