Введение: 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