Статьи

Нежное введение в GraphQL в Java World

Многие думают, что GraphQL предназначен только для Front End и JavaScript, что ему не место в технологиях Backend, таких как Java, но это действительно так.

Также очень часто GraphQL сравнивают с REST, но оправдано ли это сравнение или нет?

Во-первых, позвольте мне начать с ответа на самый важный вопрос из всех. Что такое GraphQL?

Если вы проверите официальный сайт, вы увидите что-то вроде этого

«GraphQL — это язык запросов для вашего API и среда выполнения на стороне сервера для выполнения запросов с использованием системы типов, которую вы определяете для своих данных. GraphQL не привязан к какой-либо конкретной базе данных или хранилищу, а поддерживается существующим кодом и данными ».

Что на самом деле это должно сказать

GraphQL — это спецификация, не более и не менее.

Это важно помнить, так как мы, как разработчики, будем работать с реализациями GraphQL. Некоторые реализации реализовали более или менее вещи из спецификации GraphQL. Существуют реализации на многих языках, таких как JavaScript, Java, PHP, Go и другие. Каждый день появляются новые реализации на разных языках и на существующих.

Если вы пришли из Java-фона и сделали много REST API, первое, что вас заинтересовало бы, это то, как GraphQL отличается от традиционного REST API, который вы разрабатывали годами.

Позвольте мне поместить это в контекст простого блога, который состоит из постов блога, авторов постов блога, и есть возможность размещать комментарии к постам блога.

С точки зрения БД это будет означать, что у нас есть три таблицы

Предположим, что интерфейс доступен только для чтения, и получим данные из API REST Traditional, а затем представим данные пользователю. Если бы мы построили этот традиционный REST API, мы, вероятно, в итоге получили бы такой код

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@RestController
public class SimpleRestController {
    @RequestMapping(path="/authors")
    public List getAllAuthors() { ... }
    @RequestMapping(path="/authors/{id}")
    public Author getAuthorById(@PathVariable String id) { ...
    }
    @RequestMapping(path="/posts")
    public List getAllPosts(@RequestParam(value="author_id", required = false) String authId) { ... }      
    @RequestMapping(path="/comments")
    public List getAllComments(@RequestParam(value="post_id", required = false) String postId) { ... }  
}

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

  • / сообщений

чтобы получить все сообщения, затем найти сообщение, которое мы хотим, посмотреть, что такое authorId, затем позвонить

  • / authours / <идентификатор из поста>

после чего нам нужно будет позвонить

  • / comments? post_id = <идентификатор сообщения в вопросе>

чтобы получить все комментарии к этому посту.

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

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

  • / graphql

Эта конечная точка будет получать все запросы для вашего API и отправлять обратно все ответы.

Поначалу это может показаться немного странным. Самый простой способ следовать — иметь полный код рабочего примера. Я буду использовать фрагменты кода из одного такого примера. Чтобы получить полный код, просто нажмите этот URL https://github.com/vladimir-dejanovic/simple-springboot-graphql-mongo-conftalk-demo

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
type Author {
  id: ID!
  name: String!
  posts: [Post]
 }
type Post {
  id: ID!
  title: String!
  body: String
  createdBy: Author!
  comments: [Comment]
}
 type Comment {
  id: ID!
  createdBy: Author!
  belongsTo: Post!
  text: String
 }
schema {
  query: Query
}
type Query {
  allPosts: [Post]
  allAuthors: [Author]
}

Мы начнем с определения типов, и типы могут быть почти 1 к 1 с POJO, которые мы создали бы для наших таблиц. Сначала мы вводим имя, затем набираем. Персонаж ‘ ! ‘имеет особое значение, и это означает, что поле является обязательным. Если поле имеет этот символ и его нет в ответе, это будет неверный ответ, и GraphQL не отправит ответ обратно, но отправит соответствующую ошибку.

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

Если вы проверите тип Автор, вы увидите, что у него есть поля сообщений типа Array of Post. Кроме того, пост имеет поле созданное типом «Автор» и комментариями типа «массив комментариев». Эти поля отсутствуют в POJO

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Author.java
public class Author {
 
    private final String id;
   
    private final String name;
.....get/set
}
Post.java
public class Post {
    private final String id;
   
    private String authorId;
   
    private final String title;
    private final String body;
...get/set
}

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

1
2
3
schema {
  query: Query
}

Здесь мы определяем взаимодействие с пользователем. Мы говорим, что пользователь может читать данные, используя запрос типа Query, определенный ниже.

1
2
3
4
type Query {
  allPosts: [Post]
  allAuthors: [Author]
}

Запрос — это особый тип, поскольку у нас нет этих данных в БД, это фактически наша конечная точка в традиционном мышлении.

Если вы загрузили код по ссылке GitHub, скомпилировали и запустили его, вы можете перейти по адресу http: // localhost: 8080 / . Тогда вы увидите красивый пользовательский интерфейс под названием GraphiQL . Вы можете использовать GraphiQL для игры с GraphQL API

Чтобы получить все сообщения с их идентификатором, названием и телом, просто введите это в GraphiQL

1
2
3
4
5
6
7
query {
  allPosts {
    id
    title
    body
  }
}

Ответ должен выглядеть примерно так

01
02
03
04
05
06
07
08
09
10
11
12
{
       "data": {  
         "allPosts": [    
         {
          "id": "59f4c12e7718af0b1e001072",
          "title": "Who is Ed Wong",
          "body": "Edward Wong Hau Pepelu .....”
         },
         . . . .
}

если, например, мы не были заинтересованы в теле, мы могли бы ввести что-то вроде этого

1
2
3
4
5
6
7
query {
  allPosts {
    id
    title
  }
}

ответ будет таким

01
02
03
04
05
06
07
08
09
10
11
12
{
       "data": {  
         "allPosts": [    
         {
          "id": "59f4c12e7718af0b1e001072",
          "title": "Who is Ed Wong",
         },
         . . . .
}

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

Java-код, который необходим для этого, не так уж и велик. Во-первых, нам нужно определить сервлет, который расширяет SimpleGraphQLServlet

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class GraphQLEntryPoint extends SimpleGraphQLServlet {
   
    public GraphQLEntryPoint(PostRepository postRepository, AuthorRepository authRepository, CommentRepository commentRepository) {
        super(buildSchema(postRepository,
                          authRepository,
                          commentRepository));
    }
    private static GraphQLSchema buildSchema(PostRepository postRepository, AuthorRepository authRepository, CommentRepository commentRepository) {
        return SchemaParser
                .newParser()
                .file("schema.graphqls")
                .resolvers(
                        new Query(postRepository,
                                  authRepository),
                        new PostResolver(authRepository,
                                         commentRepository),
                        new AuthorResolver(postRepository),
                        new CommentResolver(authRepository,
                                            postRepository))
                .build()
                .makeExecutableSchema();
    }  
}

Здесь я создаю синтаксический анализатор схемы, который открывает мой файл схемы GraphQL, после чего добавляются преобразователи, а затем вызываются методы build и makeExecutableSchema.

Важная часть здесь — резольверы. Resolvers — это классы, которые GraphQL будет использовать для разрешения пользовательских запросов.

Для начала самым важным является класс Query . Это не совпадение, что оно имеет то же имя, что и тип Query в схеме. Таким образом, реализация java GraphQL знает, какой класс соответствует логике запроса из схемы. Вы можете использовать любое имя, которое вам нравится, если у класса такое же имя, как у этого, однако, это будет означать, что новым людям тоже нужно будет это знать, поэтому придерживайтесь стандартного уровня, а для запросов только для чтения используйте Query.

Вот код для класса Query

01
02
03
04
05
06
07
08
09
10
11
12
13
public class Query implements GraphQLRootResolver {
    private final PostRepository postRepository;
    private final AuthorRepository authRepo;
    public List<Post> allPosts() {
        return postRepository.findAll();
    
   
    public List<Author> allAuthors() {
            return authRepo.findAll();
    }
}

Он реализует GraphQLRootResolver , и, как вы можете видеть, имеет один метод для каждой строки из схемы GraphQL.

Есть метод allPost, который возвращает список Post, а также метод allAuthors, который возвращает список Author. Это все, что нужно для того, чтобы наш API работал.

Если вы вернетесь в GraphiQL и введете ввод, как это

1
2
3
4
5
6
7
8
9
query {
  allPosts {
    id
    title
    createdBy {
      name
     }
  }
}

ответ будет что-то вроде этого

01
02
03
04
05
06
07
08
09
10
11
12
13
{
       "data": {
         "allPosts": [
           {
             "id": "59f4c12e7718af0b1e001072",
             "title": "Who is Ed Wong",
             "createdBy": {
               "name": "Ed Wong”
             }
           },
           . . .
         ]
}

вы получите внезапные данные в ответ, которые не являются частью Post pojo. Как мы только что увидели, класс Query не делает никакой магии, он просто возвращает список простых pojo типа Post. Так откуда же берется информация об авторе для поля selectedBy?

Для этого нам нужно взглянуть на другой распознаватель, точнее PostResolver , поэтому давайте посмотрим на его код

01
02
03
04
05
06
07
08
09
10
11
12
13
public class PostResolver implements GraphQLResolver<Post> {
   
    private final AuthorRepository authRepository;
    private final CommentRepository commentRepository;
   
    public Author createdBy(Post post) {
        return authRepository.findOne(post.getAuthorId());
    }  
   
    public List<Comment> comments(Post post) {
        return commentRepository.findByPostId(post.getId());
    }  
}

PostResolver реализует GraphQLResolver, и мы должны сказать, для какого типа, в данном случае, это для Post . Как вы можете видеть, все поля из схемы, которые присутствовали в Post, но отсутствовали в Pojo Post, представлены здесь как методы. Существует метод createBy, который принимает аргумент типа Post и возвращает обратно Author.

Также есть метод comments, который также принимает аргумент типа Post и возвращает список Comment.

Вот и все, что нужно сделать, это то, как Java-реализация GraphQL, которую я использую в своем коде, знает, как разрешать поля, которых нет в pojo. В случае pojo это очень просто, просто вызовите соответствующий метод get, если пользователь запросил это поле, для других полей должен быть распознаватель для этого типа, который реализует GraphQLResolver, и должен быть метод с правильной сигнатурой и возвращаемым типом.

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

В традиционном REST API мы, как разработчики, полностью контролируем, как пользователь будет взаимодействовать с нашими конечными точками, какой ответ они получат, а также какой путь пользовательский запрос будет следовать в нашем коде. Как мы видели, с GraphQL это больше не так. Мы знаем, что пользователь будет обращаться к распознавателям, но не к тому, как и по какому пути Из-за этого оптимизация намного сложнее.

К счастью, не все потеряно, мы все еще можем использовать множество старых приемов для решения этих новых / старых проблем. Если, например, мы возьмем традиционный REST API, одним из способов решения проблемы высокой производительности будет наличие контроллера с конечными точками, вызывающим сервисом, а затем сервис будет выполнять тяжелую работу. В этой настройке мы могли бы кэшировать все обращения к сервису и таким простым способом получить хорошую производительность. Мы можем сделать то же самое с GraphQL, с той лишь разницей, что вместо контроллеров, вызывающих сервисы, у нас будут распознаватели, вызывающие сервисы.

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

Я только показал вам, как читать данные, вы можете, конечно, также создавать / редактировать / изменять данные, и многое другое сделать с GraphQL. Я поделился с вами только тем, что касается функциональности, предлагаемой GraphQL при создании API.

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

В конце концов, GraphQL API — это REST API, усовершенствованный REST API с большим количеством функций и возможностей, если быть более точным. Вот почему хорошо спросить себя: действительно ли вам нужны функциональные возможности, которые предлагает GraphQL, и добавит ли он больше проблем или решений для вашего API и домена, для которых этот API был создан. Возможно, GraphQL — это именно то, что вам нужно, но опять же, может быть, старый добрый традиционный REST API — это все, что вам нужно.

Ресурсы

Опубликовано на Java Code Geeks с разрешения Владимира Деяновича, партнера нашей программы JCG . Смотреть оригинальную статью здесь: Нежное введение в GraphQL в Java World

Мнения, высказанные участниками Java Code Geeks, являются их собственными.