Введение: Почему GraphQL и Kotlin — идеальная пара?
В современной веб-разработке выбор правильного инструмента для создания API определяет скорость разработки и удобство поддержки. GraphQL — это язык запросов, который позволяет клиентам запрашивать только те данные, которые им нужны, избавляя от проблемы over-fetching и under-fetching, свойственной REST. Kotlin, в свою очередь, — современный, лаконичный и безопасный язык, который идеально подходит для бэкенд-разработки, особенно в связке со Spring Boot.
В этом туториале мы на практике создадим полноценный GraphQL API на Kotlin. Вы узнаете, как настроить сервер, определить схему, реализовать запросы и мутации, а также протестировать готовый API. К концу статьи у вас будет работающий проект, который можно использовать как основу для своих приложений.
1. Настройка проекта и зависимости
Первым делом создадим новый проект Spring Boot с поддержкой GraphQL. Мы будем использовать Spring for GraphQL — официальную интеграцию, которая делает работу максимально простой.
1.1. Создание проекта
Вы можете использовать Spring Initializr или любой IDE (IntelliJ IDEA). Добавьте следующие зависимости:
- Spring Web
- Spring for GraphQL
- Spring Data JPA
- H2 Database (для простоты)
В файле build.gradle.kts должны быть следующие строки:
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-graphql")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
runtimeOnly("com.h2database:h2")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
1.2. Конфигурация
Создадим простую сущность Book и репозиторий для работы с базой данных.
// Book.kt
@Entity
data class Book(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
val title: String,
val author: String,
val year: Int
)
// BookRepository.kt
interface BookRepository : JpaRepository<Book, Long>
2. Создание GraphQL схемы и контроллера
GraphQL использует строгую типизацию. Схема определяет, какие данные доступны и как их можно запрашивать. В Spring for GraphQL схему можно описать с помощью аннотаций.
2.1. Определение схемы через код
Создадим класс-контроллер, который будет обрабатывать GraphQL запросы. Используем аннотацию @QueryMapping для чтения данных и @MutationMapping для изменений.
// BookController.kt
@Controller
class BookController(private val repository: BookRepository) {
@QueryMapping
fun allBooks(): List<Book> = repository.findAll()
@QueryMapping
fun bookById(@Argument id: Long): Book? = repository.findById(id).orElse(null)
@MutationMapping
fun addBook(@Argument title: String, @Argument author: String, @Argument year: Int): Book {
val book = Book(title = title, author = author, year = year)
return repository.save(book)
}
@MutationMapping
fun deleteBook(@Argument id: Long): Boolean {
if (repository.existsById(id)) {
repository.deleteById(id)
return true
}
return false
}
}
2.2. Альтернатива: файл схемы .graphqls
Вы также можете определить схему в отдельном файле schema.graphqls в папке resources/graphql:
type Query {
allBooks: [Book]
bookById(id: ID!): Book
}
type Mutation {
addBook(title: String!, author: String!, year: Int!): Book
deleteBook(id: ID!): Boolean
}
type Book {
id: ID!
title: String!
author: String!
year: Int!
}
Оба подхода работают, но файл схемы даёт больше гибкости и наглядности.
3. Написание и тестирование запросов
Теперь, когда сервер настроен, запустим приложение и протестируем его. Spring Boot автоматически поднимает GraphQL эндпоинт по адресу http://localhost:8080/graphql.
3.1. Тестирование через GraphiQL
По умолчанию Spring for GraphQL включает встроенный инструмент GraphiQL. Откройте браузер по адресу http://localhost:8080/graphiql. Вы увидите интерфейс для отправки запросов.
Попробуем добавить книгу:
mutation {
addBook(title: "Война и мир", author: "Лев Толстой", year: 1869) {
id
title
author
year
}
}
Ответ:
{
"data": {
"addBook": {
"id": "1",
"title": "Война и мир",
"author": "Лев Толстой",
"year": 1869
}
}
}
3.2. Запрос всех книг
Теперь получим список всех книг, запрашивая только нужные поля:
query {
allBooks {
id
title
author
}
}
Обратите внимание: мы не запрашиваем поле year, и сервер его не вернёт. Это и есть главное преимущество GraphQL — клиент сам решает, какие данные получить.
3.3. Поиск по ID
query {
bookById(id: 1) {
title
author
}
}
4. Расширенные возможности и лучшие практики
Наше базовое приложение готово. Но в реальных проектах часто требуется больше: обработка ошибок, пагинация, подписки (subscriptions) и безопасность.
4.1. Обработка ошибок
Spring for GraphQL автоматически преобразует исключения в GraphQL ошибки. Вы можете кастомизировать это поведение, создав свой DataFetcherExceptionResolver:
@Component
class CustomExceptionResolver : DataFetcherExceptionResolverAdapter() {
override fun resolveToSingleError(ex: Throwable, env: DataFetchingEnvironment): GraphQLError? {
return when (ex) {
is NoSuchElementException -> GraphqlErrorBuilder.newError()
.message("Книга не найдена")
.path(env.executionStepInfo.path)
.build()
else -> null // передать стандартному