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

Начало работы с GraphQL и Spring Boot

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

1. Введение

GraphQL — это относительно новая концепция от Facebook, которая позиционируется как альтернатива REST для веб-API.

В этом руководстве мы узнаем, как настроить сервер GraphQL с помощью Spring Boot, чтобы мы могли добавить его в существующие приложения или использовать в новых.

2. Что такое GraphQL ?

Традиционные REST API работают с концепцией ресурсов, которыми управляет сервер. Мы можем манипулировать этими ресурсами некоторыми стандартными способами, следуя различным HTTP-глаголам. Это работает очень хорошо, пока наш API соответствует концепции ресурса, но быстро разваливается, когда нам нужно отклониться от него.

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

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

Он также работает в гораздо большей степени RPC, используя именованные запросы и мутации вместо стандартного обязательного набора действий. Это работает, чтобы поместить элемент управления туда, где он должен быть: разработчик API указывает, что возможно, а потребитель API указывает, что ему нужно.

Например, блог может разрешить следующий запрос:

query {
recentPosts(count: 10, offset: 0) {
id
title
category
author {
id
name
thumbnail
}
}
}

Этот запрос будет:

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

В традиционном REST API для этого требуется либо 11 запросов, один для сообщений и 10 для авторов, либо необходимо включить сведения об авторе в сведениях о сообщении.

2.1. Схемы GraphQL

Сервер GraphQL предоставляет схему, описывающую API. Эта схема состоит из определений типов. Каждый тип имеет одно или несколько полей, каждое из которых принимает ноль или более аргументов и возвращает определенный тип.

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

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

type Post {
id: ID!
title: String!
text: String!
category: String
author: Author!
}

type Author {
id: ID!
name: String!
thumbnail: String
posts: [Post]!
}

# The Root Query for the application
type Query {
recentPosts(count: Int, offset: Int): [Post]!
}

# The Root Mutation for the application
type Mutation {
writePost(title: String!, text: String!, category: String) : Post!
}

«!» в конце некоторых имен указывает, что это ненулевой тип. Любой тип, не имеющий this, может иметь значение null в ответе сервера. Служба GraphQL обрабатывает их правильно, позволяя нам безопасно запрашивать дочерние поля типов, допускающих значение NULL.

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

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

3. Представляем GraphQL Spring Boot Starter

Spring Boot GraphQL Starter предлагает фантастический способ запустить сервер GraphQL за очень короткое время . В сочетании с библиотекой GraphQL Java Tools нам нужно только написать код, необходимый для нашего сервиса.

3.1. Настройка службы

Все, что нам нужно для этого, это правильные зависимости:

<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>5.0.2</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>5.2.4</version>
</dependency>

Spring Boot автоматически подберет их и настроит соответствующие обработчики для работы.

По умолчанию это будет предоставлять службу GraphQL в конечной точке /graphql нашего приложения и будет принимать запросы POST, содержащие полезные данные GraphQL. При необходимости мы можем настроить эту конечную точку в нашем файле application.properties .

3.2. Написание схемы

Библиотека инструментов GraphQL работает, обрабатывая файлы схемы GraphQL для создания правильной структуры, а затем связывает специальные компоненты с этой структурой. Стартер Spring Boot GraphQL автоматически находит эти файлы схемы .

Нам нужно сохранить эти файлы с расширением «. graphqls », и они могут присутствовать в любом месте пути к классам. У нас также может быть столько этих файлов, сколько нужно, поэтому мы можем разделить схему на модули по желанию.

Единственное требование состоит в том, что должен быть ровно один корневой запрос и до одной корневой мутации. Мы не можем разделить это по файлам, в отличие от остальной части схемы. Это ограничение самого определения схемы GraphQL, а не реализации Java.

3.3. Корневой преобразователь запросов

Корневой запрос должен иметь специальные bean-компоненты, определенные в контексте Spring для обработки различных полей в этом корневом запросе . В отличие от определения схемы, нет ограничений на то, что для корневых полей запроса может быть только один компонент Spring.

Единственное требование состоит в том, чтобы bean-компоненты реализовывали GraphQLQueryResolver и чтобы каждое поле в корневом запросе из схемы имело метод в одном из этих классов с тем же именем:

public class Query implements GraphQLQueryResolver {
private PostDao postDao;
public List<Post> getRecentPosts(int count, int offset) {
return postsDao.getRecentPosts(count, offset);
}
}

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

  1. <поле>
  2. is<field> — только если поле имеет тип Boolean
  3. получить <поле>

Метод должен иметь параметры, соответствующие любым параметрам в схеме GraphQL, и может дополнительно принимать конечный параметр типа DataFetchingEnvironment.

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

Приведенное выше определяет метод getRecentPosts, который мы будем использовать для обработки любых запросов GraphQL для поля RecentPosts в схеме, определенной ранее.

3.4. Использование компонентов для представления типов

Каждый сложный тип на сервере GraphQL представлен компонентом Java, независимо от того, загружается ли он из корневого запроса или из любого другого места в структуре. Один и тот же класс Java всегда должен представлять один и тот же тип GraphQL, но имя класса не обязательно.

Поля внутри Java-бина будут напрямую сопоставлены с полями в ответе GraphQL на основе имени поля:

public class Post {
private String id;
private String title;
private String category;
private String authorId;
}

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

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

3.5. Преобразователи полей для комплексных значений

Иногда значение поля не является тривиальным для загрузки. Это может включать поиск в базе данных, сложные вычисления или что-то еще. В GraphQL Tools есть концепция преобразователя полей, которая используется для этой цели. Это бины Spring, которые могут предоставлять значения вместо бина данных.

Преобразователь полей — это любой компонент в контексте Spring, который имеет то же имя, что и компонент данных, с суффиксом Resolver и реализует интерфейс GraphQLResolver . Методы в bean-компоненте распознавателя полей следуют всем тем же правилам, что и в bean-компоненте данных, но также предоставляют сам bean-компонент данных в качестве первого параметра.

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

public class PostResolver implements GraphQLResolver<Post> {
private AuthorDao authorDao;

public Author getAuthor(Post post) {
return authorDao.getAuthorById(post.getAuthorId());
}
}

Важно, чтобы эти преобразователи полей загружались из контекста Spring. Это позволяет им работать с любыми другими управляемыми bean-компонентами Spring, например с DAO.

Важно отметить, что если клиент не запрашивает поле, то сервер GraphQL не будет выполнять работу по его извлечению . Это означает, что если клиент получает сообщение и не запрашивает автора, описанный выше метод getAuthor() не будет выполнен, и вызов DAO не будет выполнен.

3.6. Обнуляемые значения

Схема GraphQL имеет концепцию, согласно которой некоторые типы могут принимать значения NULL, а другие — нет.

Мы обрабатываем это в коде Java, напрямую используя нулевые значения. И наоборот, мы можем использовать новый тип Optional из Java 8 непосредственно для типов, допускающих значение NULL, и система будет делать правильные действия со значениями.

Это очень полезно, так как означает, что наш Java-код более явно совпадает со схемой GraphQL из определений методов.

3.7. Мутации

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

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

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

Мутации определяются в коде Java с помощью классов, реализующих GraphQLMutationResolver, а не GraphQLQueryResolver .

В противном случае применяются все те же правила, что и для запросов. Затем возвращаемое значение из поля Mutation обрабатывается точно так же, как и из поля Query, позволяя также извлекать вложенные значения:

public class Mutation implements GraphQLMutationResolver {
private PostDao postDao;

public Post writePost(String title, String text, String category) {
return postDao.savePost(title, text, category);
}
}

4. Знакомство с GraphiQL

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

Также можно автоматически включить веб-версию GraphiQL в наше приложение, добавив зависимость GraphiQL Spring Boot Starter:

<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphiql-spring-boot-starter</artifactId>
<version>5.0.2</version>
</dependency>

Это будет работать только в том случае, если мы размещаем наш GraphQL API в конечной точке по умолчанию /graphql; нам понадобится отдельное приложение, если это не так.

5. Резюме

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

Сочетание Spring Boot GraphQL Starter и библиотек GraphQL Java Tools невероятно упрощает добавление этой технологии в любые новые или существующие приложения Spring Boot.

Фрагменты кода можно найти на GitHub .