В этой статье мы рассмотрим пример GraphQL на Java и создадим простой сервер GraphQL со Spring Boot.
Таким цыпочкам тоже нравятся примеры GraphQL на Java со Spring Boot!
GraphQL — это язык запросов для API, который позволяет клиентам запрашивать ограниченное множество данных, в которых они нуждаются, что позволяет клиентам собирать данные в ограниченном количестве запросов. GraphQL — это строго типизированный протокол, и все операции с данными проверяются в соответствии со схемой GraphQL.
В этой статье мы рассмотрим пример GraphQL на Java и создадим простой сервер GraphQL со Spring Boot.
Добавление зависимостей Maven
Создайте пример Spring Boot приложения и добавьте следующие зависимости.
- graphql-spring-boot-starter используется для включения сервлета GraphQL и будет доступен по пути /graphql. Он инициализирует GraphQLSchema бин.
- graphql-java позволяет нам писать схемы на языке схем GraphQL, который прост для понимания.
- graphiql-spring-boot-starter предоставляет пользовательский интерфейс, с помощью которого мы сможем тестировать наши запросы на GraphQL и просматривать определения запросов.
<dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-spring-boot-starter</artifactId> <version>5.0.2</version> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-java-tools</artifactId> <version>5.2.4</version> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphiql-spring-boot-starter</artifactId> <version>5.0.2</version> </dependency>
Вот полное содержимое файла POM.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.techshard.graphql</groupId> <artifactId>springboot-graphql</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath /> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-spring-boot-starter</artifactId> <version>5.0.2</version> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-java-tools</artifactId> <version>5.2.4</version> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphiql-spring-boot-starter</artifactId> <version>5.0.2</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.8</version> <optional>true</optional> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Создание сущности и репозитория JPA
Давайте создадим простую сущность с именем Vehicle и соответствующий JPA репозиторий. Мы будем использовать Lombok, чтобы избежать написания шаблоного кода, такого как геттеры, сеттеры и так далее.
package com.techshard.graphql.dao.entity; import lombok.Data; import lombok.EqualsAndHashCode; import javax.persistence.*; import java.io.Serializable; import java.time.LocalDate; @Data @EqualsAndHashCode @Entity public class Vehicle implements Serializable { private static final long serialVersionUID = 1L; @Id @Column(name = "ID", nullable = false) @GeneratedValue(strategy = GenerationType.AUTO) private int id; @Column(name = "type", nullable = false) private String type; @Column(name = "model_code", nullable = false) private String modelCode; @Column(name = "brand_name") private String brandName; @Column(name = "launch_date") private LocalDate launchDate; private transient String formattedDate; // Getter and setter public String getFormattedDate() { return getLaunchDate().toString(); } }
Вот соответствующий репозиторий JPA.
package com.techshard.graphql.dao.repository; import com.techshard.graphql.dao.entity.Vehicle; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface VehicleRepository extends JpaRepository<Vehicle, Integer> { }
Схема GraphQL
GraphQL поставляется с собственным языком для написания схем GraphQL, который называется Schema Definition Language (SDL — язык определения схемы). Определение схемы состоит из всех функций API, доступных в конечной точке.
Типичный пример схемы GraphQL будет выглядеть так:
type Vehicle { id: ID!, type: String, modelCode: String, brandName: String, launchDate: String } type Query { vehicles(count: Int):[Vehicle] vehicle(id: ID):Vehicle } type Mutation { createVehicle(type: String!, modelCode: String!, brandName: String, launchDate: String):Vehicle }
Создайте папку graphql в папке src/main/resources и в ней создайте файл vehicleql.graphqls. Скопируйте вышеуказанное содержимое и вставьте его в файл vehicleql.graphqls. Обратите внимание, что именем файла может быть любое имя по вашему выбору. Просто убедитесь, что у имени файла есть расширение .graphqls.
В приведенной выше схеме каждый объект имеет определенный тип. Система типов в GraphQL является базовым компонентом и она представляет тип объекта, который можно получить от сервиса и полей, которые содержит объект.
В нашей схеме есть объект с именем Vehicle, который является нашим объектом домена. Тип Query представляет запрос, который можно сделать на сервер GraphQL для извлечения данных. Этот запрос является интерактивным, данные можно изменить, и новые результаты можно увидеть. Структура запроса и результат одинаковы. Это важно в мире GraphQL, потому что мы всегда получаем ожидаемый результат.
Ниже в этой статье мы увидим рабочий пример.
Тип Mutation представляет запросы, которые используются для выполнения операций записи данных.
Root Query
Объекты Query или Mutation являются основными объектами GraphQL. У них нет связанных классов данных. В таких случаях классы распознавателя (resolver) будут реализовывать GraphQLQueryResolver или GraphQLMutationResolver. Эти распознаватели будут искать методы, которые соответствуют полями в соответствующих основных типах.
Давайте определим основные распознаватели для Vehicle.
package com.techshard.graphql.query; import com.coxautodev.graphql.tools.GraphQLQueryResolver; import com.techshard.graphql.dao.entity.Vehicle; import com.techshard.graphql.service.VehicleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; import java.util.Optional; @Component public class VehicleQuery implements GraphQLQueryResolver { @Autowired private VehicleService vehicleService; public List<Vehicle> getVehicles(final int count) { return this.vehicleService.getAllVehicles(count); } public Optional<Vehicle> getVehicle(final int id) { return this.vehicleService.getVehicle(id); } }
В этом классе реализованы методы для получения одного объекта Vehicle и списка объектов Vehicle. Обратите внимание, что мы определили эти методы в нашей схеме выше.
Теперь давайте определим распознаватель мутаций.
package com.techshard.graphql.mutation; import com.coxautodev.graphql.tools.GraphQLMutationResolver; import com.techshard.graphql.dao.entity.Vehicle; import com.techshard.graphql.service.VehicleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.time.LocalDate; @Component public class VehicleMutation implements GraphQLMutationResolver { @Autowired private VehicleService vehicleService; public Vehicle createVehicle(final String type, final String modelCode, final String brandName, final String launchDate) { return this.vehicleService.createVehicle(type, modelCode, brandName, launchDate); } }
В этом классе у нас есть только один метод для создания объекта Vehicle, и это соответствует типу Mutation в нашем определении схемы.
Теперь мы определим сервис, который будет выполнять реальные транзакции.
package com.techshard.graphql.service; import com.techshard.graphql.dao.entity.Vehicle; import com.techshard.graphql.dao.repository.VehicleRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @Service public class VehicleService { private final VehicleRepository vehicleRepository ; public VehicleService(final VehicleRepository vehicleRepository) { this.vehicleRepository = vehicleRepository ; } @Transactional public Vehicle createVehicle(final String type,final String modelCode, final String brandName, final String launchDate) { final Vehicle vehicle = new Vehicle(); vehicle.setType(type); vehicle.setModelCode(modelCode); vehicle.setBrandName(brandName); vehicle.setLaunchDate(LocalDate.parse(launchDate)); return this.vehicleRepository.save(vehicle); } @Transactional(readOnly = true) public List<Vehicle> getAllVehicles(final int count) { return this.vehicleRepository.findAll().stream().limit(count).collect(Collectors.toList()); } @Transactional(readOnly = true) public Optional<Vehicle> getVehicle(final int id) { return this.vehicleRepository.findById(id); } }
Тестирование приложения
Приложение теперь готово к тестированию. Запустите приложение Spring Boot и откройте в браузере эту ссылку: http://localhost:8080/graphiql. Мы увидим хороший пользовательский интерфейс, как показано ниже.
В правой части пользовательского интерфейса мы можем изучить документацию.
Теперь запустите следующий запрос.
mutation { createVehicle(type: "car", modelCode: "XYZ0192", brandName: "XYZ", launchDate: "2016-08-16") { id } }
Это создаст строку в таблице Vehicle. Результат должен быть:
{ "data": { "createVehicle": { "id": "1" } } }
Давайте теперь запустим запрос, чтобы получить данные.
query { vehicles(count: 1) { id, type, modelCode } }
Вывод будет выглядеть так:
{ "data": { "vehicles": [ { "id": "1", "type": "bus", "modelCode": "XYZ123" } ] } }
Обратите внимание, что мы запрашиваем только ограниченное количество полей, мы можем изменить наш запрос, добавив или удалив поля и увидев новые результаты.
Вывод
В этой статье мы рассмотрели базовый пример GraphQL на Java со Spring Boot. Ознакомьтесь с подробной документацией здесь.
Полный исходный код этого руководства можно найти на GitHub.
Дальнейшее чтение
GraphQL: Core Features, Architecture, Pros, and Cons
Если вам понравилась эта статья и вы хотите больше узнать о GraphQL, ознакомьтесь с этой коллекцией учебников и статей по всем вопросам, связанным с GraphQL.
Прим. Переводчика.
На русском языке также есть «Руководство по GraphQL для начинающих»
mais
Вот что лично мне не нравится во всех этих статьях для begginer-ов что их уже миллион в интернетах и не в одной нет адекватных примеров для продакшен кода. Например, где валидация параметров в методе мутаций, error handling для разных методов. Где пример со сложной структурой объекта для мутации, что если создать/обновить нужно что-то более сложное чем 4 параметра, а например payment request. Что там с spring security, csrf и тп.
val6852 Автор
Есть более продвинутые материалы по GraphQL.
Например, The Fullstack Tutorial for GraphQL.