
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 г.