REST vs GraphQL: Ποιο, και Πότε;

Blog · 17 Jun 2024

REST vs GraphQL: Ποιο, και Πότε;

Όταν σχεδιάζεις ένα API, μία από τις πρώτες μεγάλες αποφάσεις που συναντάς είναι: REST ή GraphQL; Και τα δύο απαντούν στο ερώτημα «πώς θα μιλήσει το frontend με το backend», αλλά οι προσεγγίσεις τους διαφέρουν ριζικά. Σε αυτό το άρθρο θα τα συγκρίνω πέρα από τους θεωρητικούς ορισμούς — με πραγματικά παραδείγματα και ξεκάθαρες απαντήσεις στο «πότε ποιο».

Να το πω από την αρχή: αυτό δεν είναι ένα κείμενο «το X πέθανε, το Y νίκησε». Και τα δύο έχουν τη θέση τους, και τη σωστή απόφαση την καθορίζουν οι ανάγκες του έργου.

Μια γρήγορη υπενθύμιση πρώτα

Το REST είναι μια αρχιτεκτονική προσέγγιση που αναπαριστά πόρους (resources) μέσω URLs. Κάθε πόρος έχει το δικό του endpoint, και επενεργείς σε αυτούς τους πόρους με HTTP μεθόδους (GET, POST, PUT, DELETE).

restGET    /users/42
GET    /users/42/posts
POST   /posts
DELETE /posts/17

Το GraphQL είναι μια γλώσσα ερωτημάτων που ανέπτυξε το Facebook. Έχεις ένα μοναδικό endpoint (/graphql) και ο client δηλώνει ακριβώς ποια δεδομένα θέλει μέσα στο ερώτημα. Ο server επιστρέφει μόνο τα ζητούμενα δεδομένα.

graphqlquery {
  user(id: 42) {
    name
    posts {
      title
    }
  }
}

Η πραγματική διαφορά: ποιος διαμορφώνει τα δεδομένα;

Η πιο κρίσιμη διαφορά μεταξύ των δύο, κατά τη γνώμη μου, είναι αυτή. Στο REST, τα δεδομένα τα διαμορφώνει ο server. Όποιος σχεδιάζει το endpoint αποφασίζει τι θα επιστρέψει. Στο GraphQL, τα δεδομένα τα διαμορφώνει ο client. Το frontend ζητά ακριβώς αυτό που χρειάζεται.

Αυτή η διαφορά αγγίζει τρία συγκεκριμένα προβλήματα στην καθημερινή ανάπτυξη.

Over-fetching και under-fetching

Στο REST, για μια οθόνη όπου χρειάζεσαι μόνο το όνομα ενός χρήστη, όταν καλείς /users/42 πιθανότατα παίρνεις email, ημερομηνία εγγραφής, φωτογραφία προφίλ, διεύθυνση... όλα μαζί. Τραβάς περισσότερα απ' όσα χρειάζεσαι — αυτό λέγεται over-fetching.

Υπάρχει και το αντίστροφο. Θέλεις να δείξεις έναν χρήστη και τα τελευταία 5 άρθρα του. Στο REST συνήθως στέλνεις δύο αιτήματα:

restGET /users/42
GET /users/42/posts?limit=5

Δεν μπορείς να πάρεις ό,τι θέλεις σε ένα αίτημα — αυτό λέγεται under-fetching, και το αποτέλεσμα είναι το πρόβλημα «N+1 αιτημάτων».

Το GraphQL λύνει και τα δύο από τη ρίζα. Σε ένα ερώτημα παίρνεις ακριβώς τα πεδία που θέλεις, μαζί με εμφωλευμένες σχέσεις:

graphqlquery {
  user(id: 42) {
    name
    posts(last: 5) {
      title
      createdAt
    }
  }
}

Ένα αίτημα, μηδέν περίσσευμα. Ειδικά στα mobile, όπου κάθε byte και κάθε round-trip μετράει, αυτό είναι μεγάλο πλεονέκτημα.

Versioning

Στα REST APIs, καθώς προσθέτεις και αφαιρείς πεδία ξεκινά ο πονοκέφαλος του versioning: /v1/users, /v2/users... Για να μην σπάσεις τους παλιούς clients, πρέπει να κρατάς ζωντανές τις παλιές εκδόσεις.

Στο GraphQL γενικά δεν κάνεις versioning. Η προσθήκη νέου πεδίου δεν σπάει τα υπάρχοντα ερωτήματα· τα παλιά πεδία τα σημειώνεις με @deprecated και τα αποσύρεις σταδιακά. Το schema εξελίσσεται συνεχώς αλλά παραμένει ένα.

Type safety και τεκμηρίωση

Ένα από τα αγαπημένα μου στο GraphQL: το ίδιο το schema είναι ένα συμβόλαιο. Κάθε πεδίο έχει γνωστό τύπο, και έτσι παίρνεις «δωρεάν» αυτόματη τεκμηρίωση, αυτόματη παραγωγή τύπων (δουλεύει υπέροχα με TypeScript) και διαδραστικά εργαλεία εξερεύνησης όπως το GraphiQL. Στο REST, για να φτάσεις στο ίδιο επίπεδο χρειάζεσαι επιπλέον στρώματα όπως OpenAPI/Swagger.

Πού υπερτερεί λοιπόν το REST;

Αφού επαίνεσα τόσο το GraphQL, ας αποκαταστήσουμε την ισορροπία, γιατί το REST παραμένει η σωστή επιλογή στις περισσότερες περιπτώσεις.

Caching. Αυτό είναι το ισχυρότερο χαρτί του REST. Το REST είναι σε τέλεια αρμονία με τον φυσικό μηχανισμό caching του HTTP. Ένα αίτημα GET /users/42 μπορεί να γίνει cache παντού — ο browser, ένα CDN, ένα ενδιάμεσο επίπεδο. Στο GraphQL, αφού όλα πάνε σε ένα endpoint μέσω POST, αυτό το αυτόματο caching δεν λειτουργεί· πρέπει να διαχειριστείς το cache μόνος σου σε επίπεδο εφαρμογής (με κάτι σαν το Apollo Client).

Απλότητα. Το REST το ξέρουν όλοι. Όταν ένας νέος developer μπαίνει στην ομάδα, δεν χρειάζεται να αναρωτηθεί τι κάνει το GET /products. Το GraphQL έχει πιο απότομη καμπύλη μάθησης: resolvers, σχεδιασμός schema, πράγματα όπως το DataLoader για το πρόβλημα N+1...

Ανέβασμα αρχείων, απλό CRUD. Αν το μεγαλύτερο μέρος της δουλειάς είναι τυπικό «create-read-update-delete», το GraphQL προσθέτει περιττή πολυπλοκότητα. Το REST είναι πιο άμεσο εδώ.

Παρακολούθηση και rate limiting. Στο REST είναι εύκολο να συλλέγεις μετρικές ανά endpoint, να κρατάς logs και να βάζεις rate limits. Στο GraphQL, επειδή κάθε ερώτημα χτυπά ένα endpoint και η πολυπλοκότητα ποικίλλει, αυτά είναι πιο δύσκολα — ένας χρήστης μπορεί να στείλει ένα τεράστιο, βαθιά εμφωλευμένο αίτημα σε ένα ερώτημα.

Η ίδια δουλειά, δύο προσεγγίσεις δίπλα-δίπλα

Έστω ότι φτιάχνεις μια αρχική σελίδα blog: στοιχεία συγγραφέα + τα τελευταία 3 άρθρα του + το πλήθος σχολίων κάθε άρθρου.

Με REST (συνήθως περισσότερα από ένα αιτήματα):

javascriptconst user = await fetch('/api/users/42').then(r => r.json());
const posts = await fetch('/api/users/42/posts?limit=3').then(r => r.json());
const comments = await Promise.all(
  posts.map(p => fetch(`/api/posts/${p.id}/comments/count`).then(r => r.json()))
);

Τρία (ή και περισσότερα) round-trips, και η δουλειά του συνδυασμού στο frontend μένει σε σένα.

Με GraphQL (ένα αίτημα):

javascriptconst { data } = await fetch('/graphql', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    query: `
      query {
        user(id: 42) {
          name
          posts(last: 3) {
            title
            commentCount
          }
        }
      }
    `
  })
}).then(r => r.json());

Ένα αίτημα, και τα δεδομένα έρχονται ήδη στη μορφή που θέλεις. Αυτό το παράδειγμα είναι το κλασικό σενάριο όπου λάμπει το GraphQL: συλλογή συσχετισμένων, εμφωλευμένων δεδομένων με μία κίνηση.

Πίνακας απόφασης

ΚατάστασηΠρόταση
Απλό CRUD, λίγα συσχετισμένα δεδομέναREST
Το HTTP caching είναι κρίσιμοREST
Public API (θα το χρησιμοποιήσουν τρίτοι)REST (πιο οικείο)
Σύνθετα, εμφωλευμένα, συσχετισμένα δεδομέναGraphQL
Πολλοί διαφορετικοί clients (web + mobile + IoT) στο ίδιο APIGraphQL
Το frontend ζητά συνεχώς «πρόσθεσε κι αυτό το πεδίο»GraphQL
Μικρή ομάδα, ταχύτητα προτεραιότητα, χωρίς extra υποδομήREST

Συμπέρασμα

Ο πρακτικός μου κανόνας είναι αυτός: αν αμφιβάλλεις, ξεκίνα με REST. Φτάνει για τα περισσότερα έργα, το ξέρουν όλοι, η υποδομή είναι έτοιμη. Κάνε τη μετάβαση στο GraphQL όταν νιώσεις πραγματικό πόνο — όταν η ομάδα frontend παραπονιέται συνεχώς για over-fetching, όταν στέλνεις 5 ξεχωριστά αιτήματα για μία οθόνη, ή όταν πρέπει να σερβίρεις πολλές μορφές δεδομένων σε πολλά είδη clients.

Τα δύο δεν είναι και εχθροί. Πολλές ομάδες χρησιμοποιούν και τα δύο μαζί: REST για τα προς-τα-έξω, cacheable endpoints· GraphQL για σύνθετες εσωτερικές οθόνες. Η σωστή απάντηση δεν είναι «ποιο είναι πιο cool» — είναι «ποιος είναι ο πραγματικός πόνος αυτού του έργου».

Εσύ ποιο χρησιμοποιείς στο έργο σου, και γιατί; Με ενδιαφέρει — πες μου.

← Όλα τα άρθρα