Перейти к основному содержимому

Введение в GraphQL

· 6 мин. чтения

Задача: Наибольшая подстрока палиндром

Для заданной строки s, верните наибольшую подстроку палиндром входящую в s. Подстрока — это непрерывная непустая последовательность символов внутри строки. Стока является палиндромом, если она читается одинаково в обоих направлениях...

ANDROMEDA 42

1. Обзор

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

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

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

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

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

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

  • Запрос: это операция только для чтения, запрашиваемая на сервере GraphQL.
  • Мутация: это операция чтения-записи, запрашиваемая на сервере GraphQL.
  • Resolver: в GraphQL Resolver отвечает за сопоставление операции и кода, работающего на бэкэнде, который отвечает за обработку запроса. Это аналог бэкэнда MVC в приложении RESTFul.
  • Тип: тип определяет форму данных ответа, которые могут быть возвращены с сервера GraphQL, включая поля, которые являются границами для других типов .
  • Ввод: как тип, но определяет форму входных данных, которые отправляются на сервер GraphQL.
  • Scalar: это примитивный Type , такой как String , Int , Boolean , Float и т.д.
  • Интерфейс: интерфейс будет хранить имена полей и их аргументы, поэтому объекты GraphQL могут наследоваться от него, обеспечивая использование определенных полей.
  • Схема: в GraphQL схема управляет запросами и мутациями, определяя, что разрешено выполнять на сервере GraphQL.

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

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

  1. с помощью языка определения интерфейса GraphQL (IDL)
  2. используя один из поддерживаемых языков программирования

Давайте продемонстрируем пример с использованием 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 для создания определения схемы без всего шаблонного кода, созданного с использованием традиционного подхода IDL.

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

Чтобы создать наш пример, давайте сначала начнем импортировать необходимую зависимость, которая опирается на модуль 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» (создание, извлечение, обновление, удаление и список) для пользователей. Во-первых, давайте создадим наш пользовательский 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 («пользователь») как указание на то, что этот класс отображается GraphQL вместе с каждым полем, аннотированным @GraphQLField.

Далее мы создадим класс UserHandler . Этот класс наследует от выбранной библиотеки HTTP-коннектора (в нашем случае Ratpack) метод-обработчик, который будет управлять и вызывать функцию Resolver GraphQL . Таким образом, перенаправление запроса (полезных данных 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 («запрос») и @GraphQLName («мутация»). Эти аннотации используются для определения операций запроса и мутации соответственно.

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

  • Для операции CREATE:
{
"query": "mutation($name: String! $email: String!){
createUser (name: $name email: $email) { id name email age } }",
"parameters": {
"name": "John",
"email": "john@email.com"
}
}

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

{
"data": {
"createUser": {
"id": 1,
"name": "John",
"email": "john@email.com"
}
}
}
  • Для операции ПОЛУЧИТЬ:
{
"query": "query($id: String!){ retrieveUser (id: $id) {name email} }",
"parameters": {
"id": 1
}
}

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

{
"data": {
"retrieveUser": {
"name": "John",
"email": "john@email.com"
}
}
}

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

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

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

{
"data": {
"retrieveUser": {
"email": "john@email.com"
}
}
}

5. Вывод

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

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