Introdução: REST morreu, viva o GraphQL? (Spoiler: não)
\nSe você programa em Kotlin e desenvolve backend, certamente já ouviu falar da guerra santa: GraphQL vs REST. Em um canto do ringue — o bom e velho REST, que todo júnior conhece. No outro — o moderno GraphQL, que promete "me dê o que eu quero, mas não sei o quê, em uma única consulta".
\nEu sou um desenvolvedor backend com experiência em Kotlin e, nos últimos anos, reescrevi três projetos de REST para GraphQL e vice-versa. Spoiler: cada ferramenta é boa para sua tarefa. Neste artigo, não vamos ser fanáticos. Em vez disso, vamos analisar com exemplos reais de código quando REST em Kotlin vence e quando GraphQL é o seu salvador.
\nVamos conferir a comparação entre GraphQL e REST em Kotlin sem enrolação e marketing.
\n\n1. Resumindo: o que é o quê
\nAntes de mergulhar no código, vamos alinhar os termos. REST API é um estilo arquitetural onde cada recurso (usuário, pedido, produto) tem sua própria URL e métodos HTTP (GET, POST, PUT, DELETE). Desenvolvedores Kotlin geralmente usam Spring Boot ou Ktor para criar serviços REST.
\nGraphQL é uma linguagem de consulta onde você tem um único endpoint (geralmente /graphql), e o cliente decide quais campos precisa. Em Kotlin, as bibliotecas populares são KGraphQL (leve) e Spring for GraphQL (para fãs do Spring).
A principal diferença: REST é "o servidor dita o que retornar". GraphQL é "o cliente pede apenas o que precisa".
\n\n2. Exemplo de REST API em Kotlin (Ktor)
\nVamos começar com o clássico. Suponha que temos uma loja online. Precisamos obter dados de um usuário e seus últimos pedidos.
\nVeja como é um controlador REST típico em Ktor (framework leve para Kotlin):
\n\n// REST endpoint: GET /api/users/{id}\nfun Application.userModule() {\n routing {\n get("/api/users/{id}") {\n val userId = call.parameters["id"]?.toIntOrNull() ?: return@get call.respondText("Bad Request", status = HttpStatusCode.BadRequest)\n val user = userService.getUserById(userId) ?: return@get call.respondText("Not Found", status = HttpStatusCode.NotFound)\n call.respond(user)\n }\n }\n}\n\n// Data class para resposta\n@Serializable\ndata class UserResponse(\n val id: Int,\n val name: String,\n val email: String,\n val lastOrders: List<Order>\n)\n\nProblema: se o cliente (aplicativo móvel) precisa apenas do email do usuário, o servidor ainda retorna o objeto completo UserResponse com os pedidos. Isso é over-fetching — transferência excessiva de dados. Em redes móveis lentas, isso é doloroso.
Solução número um: criar vários endpoints menores: GET /api/users/{id}/email. Mas isso aumenta o número de requisições (under-fetching).
3. Exemplo de GraphQL em Kotlin (Spring for GraphQL)
\nAgora o mesmo cenário, mas com GraphQL. Veja o schema (schema.graphqls):
\n\ntype Query {\n user(id: ID!): User\n}\n\ntype User {\n id: ID!\n name: String\n email: String\n lastOrders: [Order]\n}\n\ntype Order {\n id: ID!\n total: Float\n date: String\n}\n\nE este é o código Kotlin usando Spring for GraphQL (abordagem com anotações):
\n\n@Controller\nclass UserController(private val userService: UserService) {\n\n @QueryMapping\n fun user(@Argument id: Int): User {\n return userService.getUserById(id) ?: throw UserNotFoundException("User not found")\n }\n}\n\n// Data class sem campos extras\ndata class User(val id: Int, val name: String, val email: String, val lastOrders: List<Order>)\n\nAgora o cliente pode solicitar apenas o que precisa:
\n\nquery {\n user(id: 42) {\n email\n }\n}