GraphQL — это и язык, и инструмент, который может упростить ваш API и сэкономить несколько часов разработки вашего проекта, поскольку вам не нужно создавать разные конечные точки для одной структуры данных. С Quarkus это можно сделать очень легко с расширением vertx.
Начнем с простого примера. У вас есть простой сервис, который возвращает проектные команды и пользователей для них. Вам понадобиться:
- Создайте конечную точку, чтобы получить все команды + создайте конечную точку, чтобы получить одну команду
- Создайте конечную точку, которая предоставит вам всех пользователей команды.
- Кроме того, вы можете использовать ORM для возврата команд с пользователями в них как один большой результат.
- Конечная точка, которая будет возвращать только команды с пользователями, но пользователи имеют только имя.
Я включил № 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 г.