Startseite / Artikel

REST gegen GraphQL: Die tatsächlichen Kompromisse hinter jeder Architektur

Erklärt die spezifischen Probleme, die REST und GraphQL jeweils lösen, ihre internen Mechanismen sowie die verborgenen Kompromisse, die vor der Auswahl eines für Ihre API berücksichtigt werden müssen.

Verlag: REACTAPP.TOP10 Min. Lesezeit2224 Wörter

Quellen: REST vs GraphQL: Which Problem Does Each One Actually Solve?

Wenn man darüber nachdenkt, wie Klient und Server miteinander kommunizieren sollten, stellt sich fast immer die gleiche Frage: REST oder GraphQL?

Diskussionen zu diesem Thema werden oft als Wettstreit dargestellt, als müsse eine Seite unbedingt gewinnen. Einige Entwickler argumentieren, dass REST bereits ausgedient hat und durch etwas anderes ersetzt werden sollte. Andere behaupten, GraphQL führe zu Komplexitätsstufen, die die meisten Projekte nicht benötigen. Keine dieser Sichtweisen ist besonders nützlich.

Technologien sind nicht dazu da, in Debatten Punkte zu sammeln. Sie dienen dazu, konkrete Probleme zu lösen, mit denen Entwickler unter bestimmten Umständen konfrontiert sind.

GraphQL einfach deshalb zu übernehmen, weil es aktuell ist, kann dazu führen, dass man sich mit Caching-Problemen sowie teuren Serverseitigen Abfragen herumschlagen muss, die man nicht geplant hatte. Umgekehrt kann das Aufzwingen einer starren REST-Struktur auf eine datenintensive Mobile-App dazu führen, dass die Benutzeroberfläche mit einer Flut von sequenziellen Netzwerkanfragen überlastet wird.

Um eine fundierte Entscheidung zu treffen, hilft es, den Hype beiseitezulegen und sich auf die zugrundeliegenden Motivationen zu konzentrieren: Was jede Herangehensweise eigentlich beheben sollte, wie sie intern funktioniert und welche Kompromisse damit verbunden sind.

Welches Problem hat REST gelöst?

Bevor REST zur Norm wurde, verließen sich Webdienste stark auf Protokolle wie SOAP und XML-RPC. Diese erforderten starre XML-Schemata, umfangreiche Nachrichtenstrukturen sowie aufwändige Einrichtungen. Die Clients mussten die genauen Namen der Fernverfahren kennen, um Daten abrufen zu können.

Roy Fielding führte im Jahr 2000 REST (Representational State Transfer) ein.

In seinem Kern bot REST ein einfaches, aber effektives Konzept: Sich auf die eigene Architektur des Internets zu stützen.

Anstatt einen Server als undurchsichtigen Funktionsausführungsmotor zu betrachten, modelliert REST ein System als eine Sammlung von Ressourcen, wobei jede über eine klare URL adressierbar ist. Aktionen an diesen Ressourcen werden durch standardmäßige HTTP-Verben (GET, POST, PUT, DELETE) ausgedrückt, während das Ergebnis jeder Anfrage über bekannte HTTP-Statuscodes mitgeteilt wird – wie 200 OK für Erfolg, 404 Not Found, wenn eine Ressource fehlt, oder 500 Server Error, wenn im Backend etwas schiefgeht.

Resource-Oriented Architecture (REST)
Client ─── GET /api/users/12 ──────────> Server (Find user #12)
Client <── 200 OK [ { user data } ] ──── Server
Client ─── DELETE /api/posts/99 ───────> Server (Delete post #99)
Client <── 204 No Content ───────────── Server

Warum REST wichtig ist

  • Einfachheit und Vorhersehbarkeit: Sobald man verstanden hat, dass /api/products auf eine Sammlung von Produkten verweist und /api/products/42 auf ein bestimmtes Element zeigt, wird die Schnittstelle selbstverständlich.
  • Integriertes HTTP-Caching: Da GET-Anfragen idempotent sind und auf eindeutige URLs verweisen, können Infrastrukturen wie Browser, CDN-Dienste (zum Beispiel Cloudflare) sowie Reverse-Proxy-Server (wie Nginx) die Antworten ohne zusätzliche Anwendungslogik direkt cachen.
  • Trennbare Lebenszyklen: Backend-Teams können die internen Datenbank-Schemata frei umstrukturieren, solange der öffentliche HTTP-Vertrag konstant bleibt.
  • Dieses Modell hat Entwicklern lange Zeit gute Dienste geleistet. Doch mit der zunehmenden Komplexität von Client-Anwendungen traten bestimmte Probleme zutage.

    Die neuen Herausforderungen: Über- und Unterabrufen

    Stellen Sie sich eine Mobile-App vor, die ein Benutzerprofil anzeigt. Für dieses Bildschirm benötigt man genau drei Dinge:

    • Den Namen des Benutzers
    • Eine URL, die auf das Avatar des Benutzers verweist
    • Die Titel der drei neuesten Blogbeiträge des Benutzers

    Mit einer herkömmlichen REST API führt die Erfüllung dieser bescheidenen Anforderung oft zu zwei unterschiedlichen Problemen.

    1. Überabfrage

    Der Client sendet die Anfrage GET /api/users/12.

    Der Server antwortet mit dem vollständigen Benutzerprofil:

    {
      "id": 12,
      "name": "Sarah Connor",
      "avatarUrl": "https://cdn.example.com/sarah.jpg",
      "email": "sarah@example.com",
      "address": { "street": "...", "zip": "...", "country": "..." },
      "billingHistory": [ ... ],
      "preferences": { ... },
      "createdAt": "2021-04-12T08:14:00Z"
    }
    

    Der Bildschirm benötigte lediglich name und avatarUrl, doch es wurden zusätzlich unerwünschte Adressdaten, Zahlungsinformationen sowie Einstellungen zur Darstellung heruntergeladen. Über eine langsame Mobilverbindung verschwendet das Herunterladen unnötiger Datenstrom Energie und verlangsamt die Darstellung. Dieses Szenario wird als Überabfrage bezeichnet.

    2. Unterabfrage (Das N+1-Netzwerkproblem)

    Der Client benötigt weiterhin Sarahs Beiträge, doch die Antwort auf /api/users/12 enthielt diese nicht.

    Daher wird ein zweiter Aufruf gestartet: GET /api/users/12/posts.

    Nehmen wir an, dass dieser Endpunkt nur eine einfache Liste der Post-Identifikatoren zurückgibt. In diesem Fall muss der Client möglicherweise drei separate Nachfragen senden – eine, um /api/posts/101 abzurufen, eine zweite, um /api/posts/102 zu holen, und eine dritte, um /api/posts/103 zu erhalten.

    Bis zu diesem Zeitpunkt hat das Gerät bereits mehrere aufeinanderfolgende Netzwerkaufrufe durchgeführt, nur um einen einzigen Bildschirm anzuzeigen. Dieses Muster wird als Unterabruf bezeichnet.

    Welches Problem hat GraphQL tatsächlich gelöst?

    Facebook stieß etwa im Jahr 2012 genau auf dieses Engpassproblem, als es seine Flaggschiff-App von mobilen Web-Umgebungen hin zu nativen Anwendungen umstellte. Die Mobilverbindungen waren unzuverlässig und langsam, und Facebooks Benutzeroberfläche bestand aus eng miteinander verbundenen Daten – Nutzer, Freunde, Kommentare, Likes, Reaktionen.

    Das Entwerfen eines dedizierten REST-Endpunkts für jeden Bildschirm, wie zum Beispiel /api/mobile-profile-screen, entwickelte sich schnell zu einem unübersichtlichen Muster.

    Um dieses Problem zu lösen, entwickelte Facebook GraphQL.

    GraphQL ist keine Datenbanktechnologie. Es handelt sich um eine Abfragesprache für APIs, die mit einer Server-seitigen Ausführumgebung kombiniert ist, die Abfragen gegen ein von Ihnen definiertes Typensystem ausführt.

    Anstatt mehrere Anfragen an verschiedene Endpunkte zu senden, sendet der Client eine einzige POST-Anfrage mit einer strukturierten Abfrage, die genau angibt, welche Datenvorlage benötigt wird. Der Server gibt anschließend eine JSON-Datei zurück, die genau dieser Vorlage entspricht.

    Der GraphQL-Ansatz

    So würde derselbe Profilbildschirm seine Daten mithilfe von GraphQL anfordern:

    GraphQL

    query GetUserProfile {
      user(id: "12") {
        name
        avatarUrl
        posts(limit: 3) {
          title
        }
      }
    }
    

    Und der Server gibt genau das zurück, was angefordert wurde – nichts mehr:

    JSON

    {
      "data": {
        "user": {
          "name": "Sarah Connor",
          "avatarUrl": "https://cdn.example.com/sarah.jpg",
          "posts": [
            { "title": "Building Reliable Systems" },
            { "title": "Understanding Memory Leaks" },
            { "title": "A Guide to Clean Architecture" }
          ]
        }
      }
    }
    

    Durch ungenutzte Felder entsteht kein unnötiger Bandbreitenverbrauch, und es ist nicht notwendig, mehrmals Daten abzurufen. Ein einziger Netzwerkaufruf liefert genau die Daten, die die Ansicht zum Rendern benötigt.

    Ein Blick hinter die Kulissen

    Um die damit verbundenen Abwägungen wirklich zu verstehen, hilft es, zu betrachten, wie jede Methode die Ausführung auf der Serverseite handhabt.

    RESTs endpointbasiertes Modell

    In einem Framework wie Express.js werden eingehende URLs direkt auf Handler-Funktionen abgeleitet:

    JavaScript

    // Express.js REST route
    app.get('/api/users/:id', async (req, res) => {
      const user = await db.users.findById(req.params.id);
      if (!user) return res.status(404).json({ error: 'User not found' });
      res.json(user);
    });
    

    Der Backend-Entwickler hat volle Kontrolle über die Abfragenlogik, die Datenbankaufrufe sowie die Struktur der Antwort. Die Leistungsoptimierung ist in diesem Ansatz einfach: Man geht direkt zum entsprechenden Endpoint-Handler und optimiert dort die SQL-Anweisung oder fügt Caching hinzu.

    GraphQLs Schema-und-Resolver-Modell

    GraphQL basiert auf einem Schema zusammen mit einzelnen Funktionen, die als Resolver bezeichnet werden:

    JavaScript

    // Type definitions
    const typeDefs = `
      type Post {
        id: ID!
        title: String!
      }
    
      type User {
        id: ID!
        name: String!
        avatarUrl: String!
        posts: [Post]
      }
    
      type Query {
        user(id: ID!): User
      }
    `;
    
    // Resolvers map fields to actual data sources
    const resolvers = {
      Query: {
        user: async (_, { id }) => {
          return await db.users.findById(id);
        },
      },
      User: {
        posts: async (parentUser) => {
          // Runs when the client query includes the 'posts' field
          return await db.posts.findByAuthorId(parentUser.id);
        },
      },
    };
    

    Wenn eine Abfrage eingeht, prüft der GraphQL-Engine diese gegen das Schema und durchläuft anschließend die Resolver, wobei sie in einem baumartigen Muster ausgeführt werden.

    Dies gibt Frontend-Teams viel Freiheit bei der Gestaltung ihrer Anfragen. Es gibt jedoch einen wichtigen Aspekt, den man beachten sollte: der Client bestimmt die Form der Abfrage, doch es ist der Server, der die Kosten für deren Ausführung tragen muss.

    Was GraphQL Ihnen im Hintergrund kostet

    Diese Flexibilität kommt nicht umsonst. Der Wechsel von REST zu GraphQL verschiebt die Komplexität effektiv vom Client auf den Server.

    Das N+1-Abfragen-Problem auf der Datenbankebene

    Betrachten Sie nochmals die obige Einstellung des Resolvers. Was passiert, wenn ein Client nach einer Liste von 20 Benutzern zusammen mit den Beiträgen jedes Benutzers fragt?

    GraphQL

    query {
      users {
        name
        posts {
          title
        }
      }
    }
    
    • Der users-Resolver wird einmal ausgeführt und holt alle 20 Zeilen auf einmal ab (SELECT * FROM users).
    • Anschließend wird der posts-Resolver separat für **jeden einzelnen Benutzer**, der zurückgegeben wird, ausgeführt.
    • Das Endergebnis: Ihre Datenbank muss eine Abfrage für die Benutzerliste sowie 20 zusätzliche Abfragen für die Beiträge verarbeiten – insgesamt 21 Abfragen.

    Mit REST würde man dies in der Regel durch eine SQL-JOIN-Anweisung innerhalb des Endpoint-Codes zusammenfassen (SELECT * FROM users JOIN posts...). Mit GraphQL benötigt man Batching-Tools wie DataLoader, um diese verstreuten Abfragen zu sammeln und vor dem Senden an die Datenbank zusammenzuführen.

    Die Probleme mit HTTP-Caching

    Betrachten Sie einen REST-Aufruf wie folgt:

    GET /api/articles/42
    

    Ein CDN kann diese Antwort gerne beispielsweise 10 Minuten lang im Cache speichern. Wenn während dieser Zeit 5.000 Benutzer auf /api/articles/42 zugreifen, muss der Origin-Server nur den ersten Anfragen bearbeiten – der CDN übernimmt den Rest.

    GraphQL durchbricht dieses Muster, da fast jede Operation als POST-Anfrage an ein einzelnes Endpunkt, nämlich /graphql, gesendet wird.

    Da die eigentliche Abfrage im Anfragekörper und nicht in der URL enthalten ist, haben herkömmliche HTTP-Caches, Proxy-Server und CDNs keine Möglichkeit, zu erkennen, dass zwei Anfragen identische Daten abrufen. Um dies zu umgehen, benötigt man entweder einen ausgefeilten normalisierten Cache auf der Client-Seite (zum Beispiel Apollo Client oder Relay) oder ein speziell dafür konzipiertes Caching-Gateway.

    Abfragediebstiefe als Angriffsvektor

    Mit REST gibt es keine einfache Möglichkeit für einen Angreifer, einen einzelnen Endpunkt dazu zu nutzen, in beliebig tief strukturierte relationale Daten vorzudringen.

    Mit GraphQL hingegen könnte ein bösartiger Akteur etwas wie Folgendes einreichen:

    GraphQL

    query EvilQuery {
      user {
        posts {
          author {
            posts {
              author {
                posts {
                  title
                }
              }
            }
          }
        }
      }
    }
    

    Fehlen Schutzmaßnahmen wie eine Bewertung der Abfragedichte oder eine Begrenzung der Tiefe, kann eine solche Abfrage Ihre Datenbank zum Stocken bringen oder den verfügbaren Speicher des Servers aufbrauchen.

    REST und GraphQL nebeneinander

    FeatureRESTGraphQLDatenabrufGibt für jede URL eine feste Datenstruktur zurückGibt genau die Felder, nach denen der Client fragtNetzwerkaufrufeErfordert oft mehrere Hin- und RückreisenWird in der Regel in einer Anfrage gelöstHTTP-CachingIntegriert, einfach und effizientSchwierig umzusetzen; erfordert zusätzliche Tools am Client oder GatewayLernkurveFlach, basiert auf bekannten HTTP-KonventionenSteiler, erfordert Verständnis von Schemata und ResolvernFehlerberichterstattungKonventionelle HTTP-Statuscodes (404, 500, 401)Fast immer 200 OK, wobei Probleme im errors-Feld aufgelistet werdenDateiuploadsEinfach über multipart form data möglichErfordert benutzerdefinierte Mutation-Logik oder vorsignierte Upload-URLsIdeale AnwendungsfälleCRUD-basierte Dienste, Microservices, offene Web-APIsReiche Benutzeroberflächen und Apps für verschiedene Plattformen

    Die Anfrage stellen: Wie man sich entscheidet

    Wann REST besser geeignet ist

    • Ihr Ressourcenmodell ist einfach strukturiert. Für typische CRUD-basierte Produkte wie Admin-Panels, Dashboards oder einfache Online-Shops ermöglicht REST eine schnellere Bereitstellung.
    • Caching ist für Sie von großer Bedeutung. Für öffentlichen, stark lesenden Inhalt wie Blogs, Nachrichtenseiten oder Kataloge ist das native HTTP-Caching über CDN in Bezug auf Geschwindigkeit und Kosten kaum zu übertreffen.
    • Sie stellen eine API für externe Nutzer bereit. Externe Entwickler können bereits REST-Endpunkte mit curl oder fetch aufrufen, ohne eine spezielle Client-Bibliothek lernen zu müssen.

    Wann GraphQL sinnvoller ist

    • Ihr unterstützt viele verschiedene Client-Typen. Stellen Sie sich ein Produkt vor, das iOS-, Android-, Smartwatch- und Web-Client-Versionen hat, wobei jede Version einen anderen Teil derselben zugrundeliegenden Daten benötigt.
  • Ihr Domain ist ein eng miteinander verbundenes Netzwerk. Das gilt, wenn Entitäten wie Benutzer, Gruppen, Kommentare, Berechtigungen und Medien ständig aufeinander verweisen.
  • Frontend und Backend arbeiten nach getrennten Zeitplänen. In größeren Teams dient GraphQL als Pufferlayer: Sobald Resolver und Typen definiert sind, können Frontend-Entwickler neue Ansichten bereitstellen, ohne darauf warten zu müssen, dass das Backend-Team bei jedem Zyklus neue Endpunkte hinzufügt.
  • Zusammenfassung

    Absolutes keine dieser Technologien wurde durch die andere überflüssig.

    • REST hat das Bedürfnis nach einer konsistenten, skalierbaren und cachefreundlichen Methode zur Bereitstellung von Ressourcen im Web erfüllt.
    • GraphQL hat das Bedürfnis der Clients erfüllt, ihre eigenen Datenanfragen zu gestalten, wodurch unnötige Hin- und Herbewegungen über langsame oder unzuverlässige Mobilverbindungen reduziert werden.

    Die Wahl zwischen diesen beiden Ansätzen geht nicht darum, Trends zu folgen, sondern darum, die tatsächlichen Engpässe Ihres Systems zu verstehen. Wenn Ihre größten Herausforderungen die Leistung im mobilen Umfeld sowie eine stetig wachsende Liste an frontend-spezifischen Endpunkten sind, kann GraphQL eine starke Lösung sein. Wenn Ihnen vor allem Einfachheit, zuverlässiges Caching sowie ein leicht wartbarer Backend-Teil wichtig sind, bleibt REST weiterhin eine solide und bewährte Option.