Простой сервер с GraphQL вместо REST, реализация на java

от автора


Мне предложили познакомиться с GraphQL. Посмотреть, можно ли применить в работе. Поискав я понял, что в основном информация на английском и частично старая, там 3 версия библиотеки, а уже 5 есть. Хочу восполнить этот пробел. В данном варианте будет пример на сервлетах, т.е. без spring и без spring-boot.

Теоретическая часть:
GraphQL — новый взгляд на API. Ч.1 от VladimirZaets
Сравнение REST и GraphQL от bevalorous

Сразу покажу код, потому что GraphQL это абстракция. А если долго обсуждать абстракцию то можно потеряться. Оригинал тут.

Я немного изменил код, т.к. в новых версиях нет некоторых классов.

Создаем пустой мавен проект. В помник добавляем зависимости:

    <dependency>       <groupId>com.graphql-java</groupId>       <artifactId>graphql-java</artifactId>       <version>8.0</version>     </dependency>     <dependency>       <groupId>com.graphql-java</groupId>       <artifactId>graphql-java-tools</artifactId>       <version>5.0.0</version>     </dependency>     <dependency>       <groupId>com.graphql-java</groupId>       <artifactId>graphql-java-servlet</artifactId>       <version>5.0.0</version>     </dependency>     <dependency>       <groupId>javax.servlet</groupId>       <artifactId>javax.servlet-api</artifactId>       <version>3.0.1</version>       <scope>provided</scope>     </dependency>

Чтобы не задумываться про сервер, возьмем jetty:

      <plugin>         <groupId>org.eclipse.jetty</groupId>         <artifactId>jetty-maven-plugin</artifactId>         <version>9.4.6.v20170531</version>       </plugin>

Отличия от туториала:

  1. Наследование от SimpleGraphQLServlet с вызовом конструктора теперь «deprecated», надо использовать builder, что при наследовании невозможно, используем композицию.
  2. В сервлете можно создать SimpleGraphQLServlet объект.
  3. GraphQLRootResolver – больше нет, можно использовать специфичные: GraphQLMutationResolver и GraphQLQueryResolver

Основа готова. Делать мы будем по туториалу, без спринга или JAX-RS. В общем обычный сервлет:

@WebServlet(urlPatterns = "/graphql") public class GraphQLEndpoint extends HttpServlet {     private SimpleGraphQLServlet graph;      public GraphQLEndpoint() {         graph = SimpleGraphQLServlet.builder(buildSchema()).build();     }      private static GraphQLSchema buildSchema() {         LinkRepository linkRepository = new LinkRepository();         return SchemaParser.newParser()                 .file("schema.graphqls")                 .resolvers(new Query(linkRepository), new Mutation(linkRepository))                 .build()                 .makeExecutableSchema();     }      @Override     protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {         graph.service(req, resp);     } }

В нем метод service передает данные в SimpleGraphQLServlet. Все на этом наше дело заканчивается.

Обычный код (дто Link и LinkRepository)

public class Link {      private final String url;     private final String description;      public Link(String url, String description) {         this.url = url;         this.description = description;     }      public String getUrl() {         return url;     }      public String getDescription() {         return description;     }  }

public class LinkRepository {      private final List<Link> links;      public LinkRepository() {         links = new ArrayList<>();         //add some links to start off with         links.add(new Link("http://howtographql.com", "Your favorite GraphQL page"));         links.add(new Link("http://graphql.org/learn/", "The official docks"));     }      public List<Link> getAllLinks() {         return links;     }      public void saveLink(Link link) {         links.add(link);     } }

Теперь код запросов (GET запросы в REST) и мутации (запросы для изменений)

import com.coxautodev.graphql.tools.GraphQLQueryResolver; public class Query implements GraphQLQueryResolver {      private final LinkRepository linkRepository;      public Query(LinkRepository linkRepository) {         this.linkRepository = linkRepository;     }      public List<Link> allLinks() {         return linkRepository.getAllLinks();     } } 

import com.coxautodev.graphql.tools.GraphQLMutationResolver; public class Mutation implements GraphQLMutationResolver {      private final LinkRepository linkRepository;      public Mutation(LinkRepository linkRepository) {         this.linkRepository = linkRepository;     }      public Link createLink(String url, String description) {         Link newLink = new Link(url, description);         linkRepository.saveLink(newLink);         return newLink;     } } 

Запускаем через jetty:run и кидаем запрос:

http://localhost:8080/graphql?query={allLinks{url}}

Получаем ответ:

{"data":  {"allLinks":   [     {"url":"http://howtographql.com"},     {"url":"http://graphql.org/learn/"}   ]  } }

И тут первый главный момент: дто у нас имеет 2 поля, url и description, а мы в ответ получили только url. И правильно ведь мы только url и попросили в запросе {allLinks{url}}. Если изменить запрос на такой:

http://localhost:8080/graphql?query={allLinks{url,description}}

ответ получаем такой:

{"data":   {"allLinks":    [     {       "url":"http://howtographql.com",       "description":"Your favorite GraphQL page"     },     {       "url":"http://graphql.org/learn/",       "description":"The official docks"     }    ]   } }

Вот теперь мы получили url и description. А все потому, что мы попросили их.

Запрос на изменение данных.

Тут немного сложнее и гораздо проще использовать UI утилиту.

1. Идем по адресу
2. Копируем весь файл index.html
3. Заменяем 2 строчки:

Эти:

<link rel="stylesheet" href="./node_modules/graphiql/graphiql.css" /> <script src="./node_modules/graphiql/graphiql.js"></script>

На эти:

<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/graphiql@0.11.2/graphiql.css" /> <script src="//cdn.jsdelivr.net/npm/graphiql@0.11.2/graphiql.js"></script>

4. Заменяем index.html в проекте …\src\main\webapp\index.html на только что списанный.

Перезапускаем проект, заходим на localhost:8080 и попадаем на такую страницу


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

Так же это дает любимые автокомплиты.

Теперь с его помощью отправим запрос на мутацию. Мутация у нас одна, «добавить ссылку».

запрос текстом:

mutation createLink{   createLink(url:"http://test.com", description:"test mutation"){ 	url 	description   } } 

Чуть позже я узнал, что достаточно слова mutation (createLink после него не обязательно писать)

mutation {   createLink(url:"http://test.com", description:"test mutation"){ 	url 	description   } } 

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

Как посмотреть запрос мутации

1. Открыть вкладку разработчика F12 и открыть network, отправляем запрос.

2. На отправленном запросе ПКМ -> copy -> copy as cURL(bash)

2.1. Для тех кто пользуется curl этого достаточно, для тех кто хочет посмотреть postman идем дальше

3. Открываем postman и слева сверху нажимаем import.

4. В открывшемся окне выбираем Paste Raw Text и вставляем туда скопированный запрос curl

И можем увидеть тело запроса:

{"query":"mutation createLink{\n  createLink(url:\"http://test.com\", description:\"test mutation\"){\n\t\turl\n    description\n  }\n}","variables":null,"operationName":"createLink"} Именно так с “\n”.

К данному этапу уже есть простой сервер, там есть хранилище, и мы выставили url для запросов.

Сама идея мне понравилась. Фронт может сам определить, какая информация ему на данный момент нужна, чем можно сэкономить трафик. При добавлении нового функционала старый продолжает работать, фронт продолжает получать только то, что нужно, ни больше, ни меньше.

Пример кода на github
2 ветки:

master — то, как сделано в официальном туториале

update_version — обновленная версия, с новыми версиями зависимостей.

Ссылки:

1. Доки
2. официальный туториал (для разных языков)
3. Видео, которое дало первое понимание


ссылка на оригинал статьи https://habr.com/post/418083/