[Из песочницы] Простой сервер с GraphQL вместо REST, реализация на java

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

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

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

    
      com.graphql-java
      graphql-java
      8.0
    
    
      com.graphql-java
      graphql-java-tools
      5.0.0
    
    
      com.graphql-java
      graphql-java-servlet
      5.0.0
    
    
      javax.servlet
      javax.servlet-api
      3.0.1
      provided
    


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

      
        org.eclipse.jetty
        jetty-maven-plugin
        9.4.6.v20170531
      


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

  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 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 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 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 строчки:

Эти:



На эти:



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

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

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

ootw_pzjaljr1l-rb-6lny6nq_m.jpeg

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

i0milb5ihkt1c5miqfeo9tjj2-s.jpeg

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

3vc-yb0jjrcehlce6i7k9kznqpu.jpeg

запрос текстом:
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, отправляем запрос.

oqzf0vmpkf1oil2cfp8dwrvheim.jpeg

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

khkhigyuoa_tv3xjnxzvmwr80j0.jpeg

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

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

ixglnmzt2ehziujmz7teumj9soc.jpeg

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

s7dtpwb4g6vpafvsm9yyhhf6ju0.jpeg

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

{"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. Видео, которое дало первое понимание

© Habrahabr.ru