Введение: Почему GraphQL и Kotlin?
В мире современной веб-разработки выбор правильного подхода к созданию API может определить успех всего проекта. GraphQL, разработанный Facebook, предлагает гибкую альтернативу REST, позволяя клиентам запрашивать только те данные, которые им действительно нужны. А Kotlin, современный язык программирования для JVM, добавляет к этому лаконичность, null-безопасность и мощную поддержку корутин.
В этом туториале мы на практике создадим полноценный GraphQL API с использованием Kotlin и фреймворка graphql-kotlin-spring-server. Вы узнаете, как настроить сервер, определить схему данных, написать резолверы и протестировать API. К концу статьи вы сможете самостоятельно внедрить GraphQL в свои проекты на Kotlin.
1. Настройка проекта и зависимости
Для начала создадим новый проект на Spring Boot с поддержкой GraphQL. Мы будем использовать Gradle как систему сборки.
1.1. Создание базовой структуры
Создайте новый проект в IntelliJ IDEA или через Spring Initializr. Добавьте следующие зависимости в build.gradle.kts:
plugins {
id("org.springframework.boot") version "3.2.0"
id("io.spring.dependency-management") version "1.1.4"
kotlin("jvm") version "1.9.21"
kotlin("plugin.spring") version "1.9.21"
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.expediagroup:graphql-kotlin-spring-server:7.0.1")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
1.2. Конфигурация приложения
Создайте файл application.yml в src/main/resources:
server:
port: 8080
graphql:
packages:
- "com.example.graphqldemo"
schema:
introspection-enabled: true
exception-handlers-enabled: true
Это базовая конфигурация. Параметр introspection-enabled: true позволяет использовать GraphiQL для тестирования.
2. Определение схемы GraphQL
В graphql-kotlin схема строится автоматически на основе ваших классов данных (data classes). Это одно из главных преимуществ — не нужно писать отдельный SDL-файл.
2.1. Создание модели данных
Предположим, мы создаем API для библиотеки книг. Определим модели:
package com.example.graphqldemo.model
data class Book(
val id: String,
val title: String,
val author: Author,
val year: Int,
val genre: String
)
data class Author(
val id: String,
val name: String,
val books: List<Book> = emptyList()
)
// Input-типы для мутаций
data class BookInput(
val title: String,
val authorId: String,
val year: Int,
val genre: String
)
2.2. Сервис для работы с данными
Создадим простой in-memory сервис, который будет хранить данные:
package com.example.graphqldemo.service
import com.example.graphqldemo.model.Author
import com.example.graphqldemo.model.Book
import com.example.graphqldemo.model.BookInput
import org.springframework.stereotype.Service
import java.util.UUID
@Service
class LibraryService {
private val authors = mutableListOf(
Author("1", "Джоан Роулинг"),
Author("2", "Джордж Оруэлл")
)
private val books = mutableListOf(
Book("1", "Гарри Поттер и философский камень", authors[0], 1997, "Фэнтези"),
Book("2", "1984", authors[1], 1949, "Антиутопия")
)
fun getBooks(): List<Book> = books
fun getBookById(id: String): Book? = books.find { it.id == id }
fun getAuthors(): List<Author> = authors
fun addBook(input: BookInput): Book {
val author = authors.find { it.id == input.authorId }
?: throw IllegalArgumentException("Автор не найден")
val newBook = Book(
id = UUID.randomUUID().toString(),
title = input.title,
author = author,
year = input.year,
genre = input.genre
)
books.add(newBook)
return newBook
}
}
3. Написание резолверов
Резолверы — это классы, которые определяют, как GraphQL обрабатывает запросы. В graphql-kotlin резолверы делятся на Query и Mutation.
3.1. Query-резолвер
Создадим класс для обработки запросов на получение данных:
package com.example.graphqldemo.resolver
import com.example.graphqldemo.model.Book
import com.example.graphqldemo.service.LibraryService
import org.springframework.stereotype.Component
@Component
class BookQueryResolver(private val libraryService: LibraryService) {
fun books(): List<Book> = libraryService.getBooks()
fun bookById(id: String): Book? = libraryService.getBookById(id)
}
Обратите внимание: graphql-kotlin автоматически сканирует компоненты Spring и регистрирует их как резолверы. Имена методов становятся полями в схеме.
3.2. Mutation-резолвер
Для изменения данных используем мутации:
package com.example.graphqldemo.resolver
import com.example.graphqldemo.model.Book
import com.example.graphqldemo.model.BookInput
import com.example.graphqldemo.service.LibraryService
import org.springframework.stereotype.Component
@Component
class BookMutationResolver(private val libraryService: LibraryService) {
fun addBook(input: BookInput): Book = libraryService.addBook(input)
}
3.3. Data-резолвер для связей
Если нужно кастомно обработать связанные данные (например, книги автора), создайте отдельный резолвер:
package com.example.graphqldemo.resolver
import com.example.graphqldemo.model.Author
import com.example.graphqldemo.model.Book
import com.example.graphqldemo.service.LibraryService
import org.springframework.stereotype.Component
@Component
class AuthorResolver(private val libraryService: LibraryService) {
fun books(author: Author): List<Book> =
libraryService.getBooks().filter { it.author.id == author.id }
}
4. Запуск и тестирование API
4.1. Запуск приложения
Создайте главный класс приложения:
package com.example.graphqldemo
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class GraphqlDemoApplication
fun main(args: Array