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

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

  1. Создайте конечную точку, чтобы получить все команды + создайте конечную точку, чтобы получить одну команду
  2. Создайте конечную точку, которая предоставит вам всех пользователей команды.
  3. Кроме того, вы можете использовать ORM для возврата команд с пользователями в них как один большой результат.
  4. Конечная точка, которая будет возвращать только команды с пользователями, но пользователи имеют только имя.

Я включил № 4, потому что у вас может быть какой-то действительно большой объект (представьте, что у пользователя есть 200 полей, и вы возвращаете тысячи из них. И вашим клиентам нужны только их имена для отображения в результатах поиска.

Для удобства копирования и вставки перейдите на Веб-сайт Quarkify

Легко с GraphQL

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

type User {
  id: Long
  name: String
}

type Team {
  id: Long
  name: String
  users: [User]
}

type Query {
  allTeams(excluding: String = ""): [Team]
}

Вкратце, у нас есть тип User с id и name, у нас также есть Team тип с тем же идентификатором, именем и, кроме того, пользователями, который представляет собой массив User. У нас также есть тип Query, где у нас есть методы, которые могут выполнять клиенты, в частности allTeams(excluding), который возвращает массив Team.

Реализация Quarkus

Пример рабочего репозитория github можно найти здесь

Во-первых, давайте добавим единственную зависимость, которая заставит все это работать.

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-vertx-graphql</artifactId>
</dependency>

Этого будет более чем достаточно для начала работы с GraphQL. Теперь нам также нужны супер простые классы данных Team и User.

Давайте также поместим наш файл GraphQL как teams.graphql в папку src/main/resources.

Теперь, когда это есть, давайте создадим класс GraphQlConfig.

@ApplicationScoped
public class GraphQlConfig {
    public static final List TEAMS = new ArrayList<>() {{
        add(new Team(1L, "Programmers",
                new User(1L, "Dmytro"),
                new User(2L, "Alex")
        ));
        add(new Team(2L, "Machine Learning",
                new User(3L, "Andrew NG")
        ));
    }};
    //TODO
}

Отлично, теперь у нас есть фальшивые данные, две команды, и у каждой команды есть свои пользователи.

Теперь давайте зарегистрируем нашу будущую реализацию GraphQL из Vertx Router.

public void init(@Observes Router router) throws Exception { 
router.route("/graphql").blockingHandler(GraphQLHandler.create(createGraphQL()));
}

Эта функция зарегистрирует наш экземпляр GraphQL и привяжет его к маршруту /graphql. Нам не хватает метода createGraphQL(), давайте напишем его.

Что происходит? Сохраняйте спокойствие, давайте посмотрим на каждую строчку

TypeDefinitionRegistry teamsSchema = getTeamSchema();

Этот метод просто прочитает файл teams.graphql и проанализирует его с помощью SchemaParser, вот как это реализовано:

private TypeDefinitionRegistry getTeamSchema() throws Exception {
        final URL resource = this.getClass().getClassLoader().getResource("teams.graphql");
        String schema = String.join("\n", Files.readAllLines(Paths.get(resource.toURI())));
        return new SchemaParser().parse(schema);
}

Мы используем ClassLoader для получения URL-адреса из ресурсов, а затем читаем его в String. Хорошо, вернемся к нашему методу createGraphQL().

RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
        .type("Query",
                builder -> builder.dataFetcher("allTeams", new VertxDataFetcher<>(this::getAllTeams))
        ).build();

Это наша точка отсечки, где мы связываем java-методы с нашей схемой, чтобы Vertx знал, что и куда возвращать. В частности, мы связываем allTeams со списком объектов Team. И в качестве входных данных мы используем VertxDataFetch со ссылкой на метод getAllTeams (которую мы напишем через секунду)

SchemaGenerator schemaGenerator = new SchemaGenerator();
GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(teamsSchema, runtimeWiring);

return GraphQL.newGraphQL(graphQLSchema).build();

Это наш последний шаг, мы связываем teamsSchema с нашим runtimeWiring и создаем окончательный объект GraphQL, который мы передаем нашему Router в предыдущем методе init().

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

private void getAllTeams(DataFetchingEnvironment env, Promise<List<Team>> future) {
    final String excluding = env.getArgument("excluding");
    future.complete(
            TEAMS.stream()
                    .filter(it -> !it.name.equals(excluding))
                    .collect(Collectors.toList())
    );
}

Этот метод имеет два параметра, DataFetchingEnvironment и Promise с выводом. Из окружения можно взять любую информацию из запроса, в нашем случае у нас env.getArgument("excluding"), потому что мы указали, что запрос может иметь этот аргумент. Затем мы фильтруем наш объект TEAMS с этим аргументом и передаем его в promse.

Вот полный код GraphQlConfig:

Это все, что нам нужно иметь. Давайте запустим наш сервер разработки с ./mvnw quarkus:dev и запросим некоторые данные. Во-первых, давайте получим все доступные данные, которые у нас есть

curl --location --request POST 'https://localhost:8080/graphql' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"query {\n allTeams{\n id\n name\n users {\n id\n name\n }\n }\n} ","variables":{}}'

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

{
    "data": {
        "allTeams": [
            {
                "id": 1,
                "name": "Programmers",
                "users": [
                    {
                        "id": 1,
                        "name": "Dmytro"
                    },
                    ...
                ]
            },
            {
                "id": 2,
                "name": "Machine Learning", 
                ...
}

Отфильтруем нашу команду Test, для этого нужно указать наш атрибут excluding. Кроме того, давайте запросим только имена пользователей. Вот как вы можете это сделать:

curl --location --request POST 'https://localhost:8080/graphql' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"query {\n allTeams(excluding: \"Programmers\"){\n id\n name\n users {\n name\n }\n }\n} ","variables":{}}'

На этот раз мы получим только одну группу с именем Machine Learning, и вы не увидите id пользователя.

{
    "data": {
        "allTeams": [
            {
                "id": 2,
                "name": "Machine Learning",
                "users": [
                    {
                        "name": "Andrew NG"
                    }
                ]
            }
        ]
    }
}

В заключении

Есть много возможностей для улучшения, но вы можете видеть, как GraphQL может упростить разработку как для Backend, так и для Frontend разработчиков. Бэкэнд-разработчикам не нужно думать о том, какие данные нужно получить пользователю (конечно, за исключением случаев использования безопасности, в этом случае вы можете получить много информации от DataFetchingEnvironment).

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

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

Первоначально опубликовано на https://quarkify.net 29 апреля 2020 г.