GraphQL vs REST в Kotlin: что выбрать для бэкенда в 2025

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: REST умер, да здравствует GraphQL? (Спойлер: нет)

Если вы пишете на Kotlin и разрабатываете бэкенд, то наверняка слышали священную войну: GraphQL vs REST. В одном углу ринга — старый добрый REST, который знает каждый джуниор. В другом — модный GraphQL, который обещает «дай мне то, не знаю что, но только одним запросом».

Я — бэкенд-разработчик с опытом на Kotlin, и за последние пару лет переписал три проекта с REST на GraphQL и обратно. Спойлер: каждый инструмент хорош для своей задачи. В этой статье мы не будем фанатеть. Вместо этого разберем на реальных примерах кода, когда REST на Kotlin выигрывает, а когда GraphQL — ваш спаситель.

Погнали смотреть на сравнение GraphQL и REST в Kotlin без воды и маркетинга.

1. Коротко о главном: что есть что

Прежде чем лезть в код, давайте договоримся о терминах. REST API — это архитектурный стиль, где каждый ресурс (пользователь, заказ, товар) имеет свой URL и методы HTTP (GET, POST, PUT, DELETE). Kotlin-разработчики чаще всего используют Spring Boot или Ktor для создания REST-сервисов.

GraphQL — это язык запросов, где у вас один endpoint (обычно /graphql), и клиент сам решает, какие поля ему нужны. На Kotlin популярны библиотеки KGraphQL (легковесная) и Spring for GraphQL (для фанатов Spring).

Ключевое отличие: REST — это «сервер диктует, что отдавать». GraphQL — «клиент просит только то, что нужно».

2. Пример REST API на Kotlin (Ktor)

Начнем с классики. Предположим, у нас есть интернет-магазин. Нужно получить данные о пользователе и его последних заказах.

Вот как выглядит типичный REST-контроллер на Ktor (легкий фреймворк для Kotlin):

// REST endpoint: GET /api/users/{id}
fun Application.userModule() {
    routing {
        get("/api/users/{id}") {
            val userId = call.parameters["id"]?.toIntOrNull() ?: return@get call.respondText("Bad Request", status = HttpStatusCode.BadRequest)
            val user = userService.getUserById(userId) ?: return@get call.respondText("Not Found", status = HttpStatusCode.NotFound)
            call.respond(user)
        }
    }
}

// Data class для ответа
@Serializable
data class UserResponse(
    val id: Int,
    val name: String,
    val email: String,
    val lastOrders: List<Order>
)

Проблема: если клиенту (мобильному приложению) нужен только email пользователя, сервер всё равно отдаст полный объект UserResponse с заказами. Это over-fetching — избыточная передача данных. На слабых мобильных сетях это больно.

Решение номер один — делать несколько мелких endpoint'ов: GET /api/users/{id}/email. Но тогда растет количество запросов (under-fetching).

3. Пример GraphQL на Kotlin (Spring for GraphQL)

Теперь тот же сценарий, но с GraphQL. Вот схема (schema.graphqls):

type Query {
    user(id: ID!): User
}

type User {
    id: ID!
    name: String
    email: String
    lastOrders: [Order]
}

type Order {
    id: ID!
    total: Float
    date: String
}

А это Kotlin-код с использованием Spring for GraphQL (аннотационный подход):

@Controller
class UserController(private val userService: UserService) {

    @QueryMapping
    fun user(@Argument id: Int): User {
        return userService.getUserById(id) ?: throw UserNotFoundException("User not found")
    }
}

// Data class без лишних полей
data class User(val id: Int, val name: String, val email: String, val lastOrders: List<Order>)

Теперь клиент может запросить только то, что нужно:

query {
    user(id: 42) {
        email
    }
}

Сервер отдаст только {"data": {"user": {"email": "test@example.com"}}}. Никакого овер-фетчинга. Красота? Да, но есть нюансы.

4. GraphQL vs REST: битва характеристик

Давайте пройдемся по ключевым критериям для Kotlin проектов.

4.1. Производительность и сложность запросов

REST здесь выигрывает по простоте. Один endpoint — один SQL-запрос (обычно). GraphQL же может породить проблему N+1, когда на каждый вложенный объект (например, заказы пользователя) делается отдельный запрос к БД. Решается это DataLoader (пакетная загрузка), но это дополнительная сложность.

Пример на Kotlin с DataLoader (библиотека graphql-kotlin-dataloader):

class UserDataLoader(private val userService: UserService) : DataLoader<Int, User>() {
    override suspend fun load(keys: List<Int>): Map<Int, User> {
        val users = userService.getUsersByIds(keys)
        return users.associateBy { it.id }
    }
}

Вывод: если у вас простые CRUD-запросы — REST быстрее и проще. Если сложные графовые структуры — GraphQL может быть эффективнее, но требует аккуратности.

4.2. Кэширование

REST тут безоговорочный лидер. HTTP-кеширование (ETag, Cache-Control) работает «из коробки». GraphQL ломает эту модель, потому что все запросы идут на один endpoint. Решения есть (Persisted Queries, CDN), но они требуют настройки.

4.3. Типизация и документация

GraphQL самодокументируем — схема является контрактом. Для REST приходится использовать Swagger/OpenAPI. Kotlin с его строгой типизацией дружит с обоими подходами, но GraphQL дает более строгий контроль на уровне схемы.

5. Когда выбирать REST, а когда GraphQL в Kotlin

Вот мой личный чек-лист, основанный на опыте.

Когда REST — ваш выбор:

  • Простое CRUD-приложение (блог, TODO-лист).
  • Нужно мощное кэширование на уровне HTTP.
  • Команда не готова разбираться с GraphQL и DataLoader.
  • Вы используете Ktor и хотите минимализм.

Когда GraphQL — ваш выбор:

  • Сложные графовые данные (соцсети, аналитика).
  • Разные клиенты (мобила, веб, IoT) требуют разные наборы полей.
  • Вы хотите избежать версионирования API (REST часто требует v2

Блоги

Рекомендаций по книгам