Einleitung: REST ist tot, es lebe GraphQL? (Spoiler: nein)
\nWenn Sie in Kotlin programmieren und ein Backend entwickeln, haben Sie sicherlich vom heiligen Krieg gehört: GraphQL vs REST. In einer Ecke des Rings – der gute alte REST, den jeder Junior kennt. In der anderen – das modische GraphQL, das verspricht: „Gib mir das, was ich nicht weiß, aber nur mit einer einzigen Anfrage.“
\nIch bin Backend-Entwickler mit Erfahrung in Kotlin und habe in den letzten Jahren drei Projekte von REST auf GraphQL und wieder zurück umgestellt. Spoiler: Jedes Werkzeug ist für seine Aufgabe gut. In diesem Artikel werden wir nicht fanatisch sein. Stattdessen analysieren wir anhand realer Codebeispiele, wann REST in Kotlin gewinnt und wann GraphQL Ihr Retter ist.
\nLos geht's mit dem Vergleich von GraphQL und REST in Kotlin ohne unnötige Füllwörter und Marketing.
\n\n1. Kurz und knapp: Was ist was
\nBevor wir in den Code eintauchen, lassen Sie uns die Begriffe klären. REST API ist ein Architekturstil, bei dem jede Ressource (Benutzer, Bestellung, Produkt) ihre eigene URL und HTTP-Methoden (GET, POST, PUT, DELETE) hat. Kotlin-Entwickler verwenden meist Spring Boot oder Ktor zur Erstellung von REST-Diensten.
\nGraphQL ist eine Abfragesprache, bei der Sie einen einzigen Endpunkt haben (normalerweise /graphql), und der Client selbst entscheidet, welche Felder er benötigt. In Kotlin sind die Bibliotheken KGraphQL (leichtgewichtig) und Spring for GraphQL (für Spring-Fans) beliebt.
Der Hauptunterschied: REST bedeutet „Der Server diktiert, was zurückgegeben wird“. GraphQL bedeutet „Der Client fordert nur das an, was er braucht“.
\n\n2. Beispiel einer REST API in Kotlin (Ktor)
\nBeginnen wir mit dem Klassiker. Angenommen, wir haben einen Online-Shop. Wir benötigen Daten über einen Benutzer und seine letzten Bestellungen.
\nSo sieht ein typischer REST-Controller in Ktor (einem leichten Framework für Kotlin) aus:
\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 für die Antwort\n@Serializable\ndata class UserResponse(\n val id: Int,\n val name: String,\n val email: String,\n val lastOrders: List<Order>\n)\n\nProblem: Wenn der Client (eine mobile App) nur die E-Mail-Adresse des Benutzers benötigt, gibt der Server trotzdem das vollständige Objekt UserResponse mit Bestellungen zurück. Dies ist Over-Fetching – übermäßige Datenübertragung. In schwachen Mobilfunknetzen ist das schmerzhaft.
Lösung Nummer eins: mehrere kleine Endpunkte erstellen: GET /api/users/{id}/email. Aber dann steigt die Anzahl der Anfragen (Under-Fetching).
3. Beispiel für GraphQL in Kotlin (Spring for GraphQL)
\nJetzt das gleiche Szenario, aber mit GraphQL. Hier ist das 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\nUnd das ist der Kotlin-Code mit Spring for GraphQL (annotationsbasierter Ansatz):
\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 ohne unnötige Felder\ndata class User(val id: Int, val name: String, val email: String, val lastOrders: List<Order>)\n\nJetzt kann der Client nur das anfordern, was er braucht:
\n\nquery {\n user(id: 42) {\n email\n }\n}