GraphQL Resolvers: Βέλτιστες πρακτικές

Από το graphql.org

Αυτή η ανάρτηση αποτελεί το πρώτο μέρος μιας σειράς βέλτιστων πρακτικών και παρατηρήσεων που πραγματοποιήσαμε κατά την κατασκευή των API GraphQL στο PayPal. Στις επερχόμενες θέσεις, θα μοιραστούμε τις σκέψεις μας σχετικά με: το σχεδιασμό σχήματος, το χειρισμό σφαλμάτων, την προβολή της παραγωγής, τη βελτιστοποίηση των ενσωματώσεων από την πλευρά του πελάτη και τη δημιουργία εργαλείων για τις ομάδες.

Μπορεί να έχετε δει την προηγούμενη ανάρτησή μας "GraphQL: Μια ιστορία επιτυχίας για το PayPal Checkout" σχετικά με το ταξίδι του PayPal από το REST στο GraphQL. Αυτή η θέση καταγράφει λεπτομερώς ορισμένες βέλτιστες πρακτικές για την ανάπτυξη λύσεων που είναι γρήγορες, δοκιμαστικές και ανθεκτικές με την πάροδο του χρόνου.

Τι είναι ένας αναλυτής;

Ας ξεκινήσουμε με την ίδια γραμμή βάσης. Τι είναι ένας αναλυτής;

Ο ορισμός του διαχωριστή
Κάθε πεδίο σε κάθε τύπο υποστηρίζεται από μια λειτουργία που ονομάζεται resolver.

Ένας αναλυτής είναι μια συνάρτηση που επιλύει μια τιμή για έναν τύπο ή ένα πεδίο σε ένα σχήμα. Οι διαχωριστές μπορούν να επιστρέψουν αντικείμενα ή βαθμίδες όπως χορδές, αριθμούς, booleans, κλπ. Αν επιστραφεί ένα αντικείμενο, η εκτέλεση συνεχίζεται στο επόμενο παιδικό πεδίο. Εάν επιστραφεί ένα βαθμωτό (συνήθως σε έναν κόμβο φύλλων), η εκτέλεση ολοκληρώνεται. Εάν επιστραφεί μηδέν, η εκτέλεση διακόπτεται και δεν συνεχίζεται.

Οι διαλυτές μπορούν να είναι ασύγχρονοι και εγώ! Μπορούν να επιλύσουν τιμές από άλλο API REST, βάση δεδομένων, cache, σταθερή κ.λπ.

Αργότερα, θα περπατήσουμε μέσα από μια σειρά παραδειγμάτων που δείχνουν πώς να οικοδομήσουμε λύσεις που είναι γρήγορες, δοκιμαστικές και ανθεκτικές.

Εκτέλεση ερωτημάτων

Για να κατανοήσετε καλύτερα τους διαχωριστές, πρέπει να ξέρετε πώς εκτελούνται τα ερωτήματα.

Κάθε ερώτημα GraphQL περνάει από τρεις φάσεις. Τα ερωτήματα αναλύονται, επικυρώνονται και εκτελούνται.

  1. Ανάλυση - Ένα ερώτημα αναλύεται σε ένα αφηρημένο δέντρο σύνταξης (ή AST). Τα AST είναι απίστευτα ισχυρά και πίσω από εργαλεία όπως ESLint, babel κ.λπ. Εάν θέλετε να δείτε τι φαίνεται το GraphQL AST, ελέγξτε το astexplorer.net και αλλάξτε το JavaScript στο GraphQL. Θα δείτε ένα ερώτημα στα αριστερά και ένα AST στα δεξιά.
  2. Επικύρωση - Η AST επικυρώνεται με το σχήμα. Ελέγχει τη σωστή σύνταξη ερωτήματος και αν υπάρχουν τα πεδία.
  3. Εκτέλεση - Ο χρόνος εκτέλεσης περνά μέσα από το AST, ξεκινώντας από τη ρίζα του δέντρου, επικαλείται resolvers, συλλέγει τα αποτελέσματα και εκπέμπει το JSON.

Για αυτό το παράδειγμα, θα αναφερθούμε σε αυτό το ερώτημα:

Ερώτημα για μεταγενέστερη αναφορά

Όταν αναλύεται αυτό το ερώτημα, μετατρέπεται σε AST ή δέντρο.

Ερώτημα που αντιπροσωπεύεται ως δέντρο

Ο τύπος ερωτήματος ρίζας είναι το σημείο εισόδου στο δέντρο και περιέχει τα δύο βασικά πεδία, τον χρήστη και το άλμπουμ. Οι λύσεις του χρήστη και του άλμπουμ εκτελούνται παράλληλα (κάτι που είναι χαρακτηριστικό μεταξύ όλων των χρόνων εκτέλεσης). Το δέντρο εκτείνεται ευρύτατα - πρώτα, που σημαίνει ότι ο χρήστης πρέπει να επιλυθεί πριν εκτελεστεί το όνομα και το email του παιδιού του. Εάν ο αναλυτής χρήστη είναι ασύγχρονος, το τμήμα χρήστη καθυστερεί μέχρι να επιλυθεί. Μόλις επιλυθούν όλοι οι κόμβοι φύλλων, όνομα, email, τίτλος, η εκτέλεση έχει ολοκληρωθεί.

Τα πεδία Ρόλων ερωτήματος, όπως ο χρήστης και το άλμπουμ, εκτελούνται παράλληλα αλλά σε καμία συγκεκριμένη σειρά. Συνήθως, τα πεδία εκτελούνται με τη σειρά που εμφανίζονται στο ερώτημα, αλλά δεν είναι ασφαλές να το υποθέσουμε αυτό. Επειδή τα πεδία εκτελούνται παράλληλα, θεωρείται ότι είναι ατομικά, ανεξέλεγκτα και έχουν παρενέργειες.

Κοιτάζοντας πιο κοντά στους διαχωριστές

Στις επόμενες ενότητες θα χρησιμοποιήσουμε το JavaScript, αλλά οι διακομιστές GraphQL μπορούν να γραφτούν σχεδόν σε οποιαδήποτε γλώσσα.

Διαχωριστές με τέσσερα επιχειρήματα - root, args, context, info

Σε κάποια ή άλλη μορφή, κάθε αναλυτής σε κάθε γλώσσα λαμβάνει αυτά τα τέσσερα επιχειρήματα:

  • root - Αποτέλεσμα από τον προηγούμενο / γονικό τύπο
  • args - Επιχειρήματα που παρέχονται στο πεδίο
  • context - ένα μεταβλητό αντικείμενο που παρέχεται σε όλους τους διαχωριστές
  • info - Ειδικές πληροφορίες σχετικά με το ερώτημα (χρησιμοποιούνται σπάνια)

Αυτά τα τέσσερα επιχειρήματα είναι βασικά για την κατανόηση του τρόπου με τον οποίο τα δεδομένα ρέουν μεταξύ των λύσεων.

Προεπιλεγμένες λύσεις

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

Προεπιλεγμένη υλοποίηση αναλυτή

Λήψη δεδομένων σε λύσεις

Από πού πρέπει να φέρουμε τα δεδομένα; Ποιες είναι οι συμφωνίες με τις επιλογές μας;

Στα επόμενα παραδείγματα, θα αναφερθούμε ξανά σε αυτό το σχήμα:

Ένα πεδίο συμβάντος έχει ένα απαιτούμενο όρισμα id, επιστρέφει ένα συμβάν

Μετάδοση δεδομένων μεταξύ των διαχωριστικών

Το περιβάλλον είναι ένα μεταβλητό αντικείμενο που παρέχεται σε όλους τους διαχωριστές. Δημιουργείται και καταστρέφεται μεταξύ κάθε αιτήματος. Είναι ένα εξαιρετικό μέρος για να αποθηκεύσετε κοινά δεδομένα Auth, κοινά μοντέλα / fetchers για API και βάσεις δεδομένων κλπ. Στο PayPal, είμαστε ένα μεγάλο κατάστημα Node.js με υποδομή που βασίζεται στην Express, έτσι αποθηκεύουμε το Express 'req εκεί.

Όταν μάθετε για πρώτη φορά το περιεχόμενο, μια πρώτη σκέψη μπορεί να είναι η χρήση του πλαισίου ως μνήμης cache γενικού σκοπού. Αυτό δεν συνιστάται, αλλά εδώ μπορεί να μοιάζει με μια εφαρμογή.

Μεταβιβάζοντας δεδομένα μεταξύ των λύσεων χρησιμοποιώντας το πλαίσιο. Αυτό δεν συνιστάται!

Όταν επικαλείται τον τίτλο, αποθηκεύουμε το αποτέλεσμα του συμβάντος στο πλαίσιο. Όταν χρησιμοποιείται το photoUrl, βγάζουμε το γεγονός από το περιβάλλον και το χρησιμοποιούμε. Αυτός ο κωδικός δεν είναι αξιόπιστος. Δεν υπάρχει εγγύηση ότι ο τίτλος θα εκτελεστεί πριν από τη φωτογραφία.

Θα μπορούσαμε να διορθώσουμε και τους δύο διαχωριστές για να ελέγξουμε αν το συμβάν υπάρχει στο πλαίσιο. Αν ναι, χρησιμοποιήστε το. Διαφορετικά, το φέρουμε και το αποθηκεύουμε αργότερα, αλλά εξακολουθεί να υπάρχει μεγάλη επιφάνεια για λάθη.

Αντ 'αυτού, θα πρέπει να αποφύγουμε τη μετάλλαξη του πλαισίου εντός των διαχωριστών. Θα πρέπει να εμποδίσουμε τη γνώση και τις ανησυχίες από την ανάμειξη μεταξύ τους, έτσι ώστε οι λύτες μας να είναι εύκολο να κατανοηθούν, να εντοπιστούν σφάλματα και να δοκιμαστούν.

Μεταβιβάζοντας δεδομένα από γονέα σε παιδί

Το επιχειρησιακό όρισμα είναι για τη μεταβίβαση δεδομένων από γονικούς διαχωρισμούς σε επίλυση παιδιών.

Για παράδειγμα, εάν δημιουργείτε έναν τύπο συμβάντος όπου όλα τα πεδία του συμβάντος
εξαρτάται από τα ίδια δεδομένα, μπορεί να θέλετε να το λάβετε μία φορά στο πεδίο συμβάντος,
και όχι σε κάθε πεδίο του Event.

Φαίνεται σαν μια καλή ιδέα, έτσι; Αυτός είναι ένας γρήγορος τρόπος για να ξεκινήσετε με την επίλυση των κτιρίων, αλλά μπορεί να αντιμετωπίσετε κάποια προβλήματα. Ας καταλάβουμε γιατί.

Για τα παρακάτω παραδείγματα, θα συνεργαστούμε με έναν τύπο συμβάντος που έχει δύο πεδία.

Τύπος συμβάντος με δύο πεδία: τίτλος και photoUrl

Τα περισσότερα από τα πεδία για το Event μπορούν να ληφθούν από ένα API Event, έτσι ώστε να το φέρουμε στον κορυφαίο αναλυτή συμβάντων και να δώσουμε τα αποτελέσματα στον τίτλο μας και τους resolvers του photoUrl.

Ο αναλυτής συμβάντων κορυφαίου επιπέδου συγκεντρώνει δεδομένα, παρέχει αποτελέσματα στον τίτλο και στους αναλυτές πεδίων photoUrl

Ακόμα καλύτερα, δεν χρειάζεται να διευκρινίσουμε τους δύο βασικούς λύτες.
Μπορούμε να χρησιμοποιήσουμε τις προεπιλεγμένες λύσεις επειδή το αντικείμενο που επιστρέφεται από το getEvent ()
έχει τίτλο και ιδιότητα photoUrl.

το όνομα και ο τίτλος επιλύονται χρησιμοποιώντας προεπιλεγμένους ανιχνευτές

Τι συμβαίνει με αυτό;

Υπάρχουν δύο σενάρια όπου ενδέχεται να αντιμετωπίσετε υπερβολική ...

Σενάριο # 1: Ανάκτηση δεδομένων πολλαπλών στρωμάτων

Ας υποθέσουμε ότι υπάρχουν ορισμένες απαιτήσεις και πρέπει να εμφανίσετε τους συμμετέχοντες σε μια εκδήλωση. Ξεκινάμε προσθέτοντας ένα πεδίο συμμετεχόντων στο Event.

Τύπος συμβάντος με ένα επιπλέον πεδίο συμμετεχόντων

Όταν φέρετε τα στοιχεία των συμμετεχόντων, έχετε δύο επιλογές: να συγκεντρώσετε αυτά τα δεδομένα στον αναλυτή συμβάντων ή τον αναλυτή των συμμετεχόντων.

Θα δοκιμάσουμε την πρώτη επιλογή: προσθέτοντάς την στον αναλυτή συμβάντων.

ο αναλυτής συμβάντων καλεί δύο API, ανακτώντας τα στοιχεία συμβάντων και τις λεπτομέρειες των συμμετεχόντων

Εάν ένας πελάτης υποβάλλει ερωτήματα μόνο για τον τίτλο και το photoUrl, αλλά όχι για τους συμμετέχοντες. Τώρα είστε αναποτελεσματικοί και κάνετε περιττό αίτημα στο API των συμμετεχόντων.

Δεν είναι δικό σας λάθος, έτσι δουλεύουμε. Αναγνωρίζουμε τα πρότυπα και τα αντιγράψουμε.
Αν οι συνεισφέροντες θεωρούν ότι η ανάκτηση δεδομένων γίνεται στον αναλυτή συμβάντων, πιθανότατα
προσθέστε τυχόν πρόσθετα δεδομένα που τραβούν εκεί χωρίς να το σκέφτεστε πάρα πολύ.

Έχουμε μια ακόμα επιλογή για να δοκιμάσουμε με την προσέλκυση των παρευρισκομένων στο εσωτερικό του αναλυτή των παρευρισκομένων.

οι συμμετέχοντες αναλυτές προσελκύουν τις λεπτομέρειες των συμμετεχόντων από το API των συμμετεχόντων

Αν οι ερωτήσεις πελατών μας αφορούν μόνο τους συμμετέχοντες, όχι τον τίτλο και το photoUrl. Εξακολουθούμε να είμαστε αναποτελεσματικοί, κάνοντας περιττό αίτημα στο API Events.

Σενάριο # 2: Πρόβλημα N + 1

Επειδή τα δεδομένα αποστέλλονται σε επίπεδο πεδίου, διατρέχουμε τον κίνδυνο υπερφορτωμάτων. Η υπερφόρτωση και το πρόβλημα N + 1 είναι ένα δημοφιλές θέμα στον κόσμο GraphQL. Shopify έχει ένα μεγάλο άρθρο που εξηγεί N + 1 καλά.

Πώς μας επηρεάζει αυτό εδώ;

Για να το απεικονίσουμε καλύτερα, θα προσθέσουμε ένα νέο πεδίο συμβάντων που επιστρέφει όλα τα συμβάντα.

Ένα πεδίο συμβάντων επιστρέφει όλα τα συμβάντα.Ερώτημα για όλα τα συμβάντα με τον τίτλο και τους συμμετέχοντες

Εάν ένας πελάτης υποβάλλει ερωτήματα για όλα τα συμβάντα και τους συμμετέχοντες τους, διατρέχουμε τον κίνδυνο υπερφορτωμάτων επειδή οι συμμετέχοντες μπορούν να παρακολουθήσουν περισσότερα από ένα συμβάντα. Μπορούμε να κάνουμε διπλότυπα αιτήματα για τον ίδιο συμμετέχοντα.

Αυτό το πρόβλημα ενισχύεται σε μια μεγάλη οργάνωση όπου τα αιτήματα μπορούν να εκτονωθούν και να προκαλέσουν περιττή πίεση στο σύστημά σας.

Για να το λύσουμε αυτό, χρειαζόμαστε παρτίδες και απαίτηση!

Στο JavaScript, μερικές δημοφιλείς επιλογές είναι dataloader και πηγές δεδομένων Apollo.

Αν χρησιμοποιείτε άλλη γλώσσα, υπάρχει κάτι που μπορείτε να πάρετε. Ρίξτε μια ματιά πριν την επίλυση αυτή τη δική σας.

Στον πυρήνα της, αυτές οι βιβλιοθήκες κάθονται πάνω από το στρώμα πρόσβασης δεδομένων σας και θα αποθηκεύουν προσωρινά και εξουδετερώνουν τα εξερχόμενα αιτήματα χρησιμοποιώντας debouncing ή memoization. Αν είσαι περίεργος σε αυτό που μοιάζει να μοιάζει με async, ελέγξτε την εξαιρετική θέση του Daniel Brain!

Ανάκτηση δεδομένων σε επίπεδο τομέα

Νωρίτερα, είδαμε ότι είναι εύκολο να γίνει καύση με υπερβολική προσπέλαση με "κορυφαίους" γονείς-σε-παιδιά λύτες.

Υπάρχει καλύτερη εναλλακτική λύση;

Ας διώξουμε ξανά την επιλογή parent-to-child. Τι γίνεται αν το αντιστρέψουμε έτσι ώστε τα πεδία παιδιού μας να είναι υπεύθυνα για την απόκτηση των δικών τους δεδομένων;

Τα πεδία είναι υπεύθυνα για τη λήψη των δικών τους δεδομένων.
Γιατί είναι μια καλύτερη εναλλακτική λύση;

Αυτός ο κώδικας είναι εύκολος στη σκέψη. Γνωρίζετε ακριβώς πού μεταφέρεται ένα μήνυμα ηλεκτρονικού ταχυδρομείου. Αυτό κάνει εύκολο το debugging.

Αυτός ο κώδικας είναι πιο δοκιμαστικός. Δεν χρειάζεται να δοκιμάσετε τον αναλυτή συμβάντων όταν πραγματικά θέλατε να δοκιμάσετε τον τίτλο αναλυτή.

Για κάποιους, το duplication getEvent μπορεί να μοιάζει με μυρωδιά κώδικα. Αλλά, έχοντας έναν κώδικα που είναι απλός, εύκολος στη λογική, και είναι πιο δοκιμαστικός αξίζει λίγο διπλασιασμό.

Αλλά, εξακολουθεί να υπάρχει ένα πιθανό πρόβλημα εδώ. Εάν ένα ερώτημα πελάτη για τίτλο και photoUrl, προκαλούμε ένα επιπλέον αίτημα στο API του Event μας με getEvent. Όπως είδαμε νωρίτερα στο πρόβλημα N + 1, θα πρέπει να απορρίψουμε τα αιτήματα σε επίπεδο πλαισίου χρησιμοποιώντας βιβλιοθήκες όπως οι πηγές δεδομένων του dataloader και του Apollo.

Εάν φέρουμε τα δεδομένα σε επίπεδο πεδίου και υποθέτουμε αιτήματα, έχουμε κώδικα που είναι πιο εύκολο να εντοπιστεί και να δοκιμαστεί και μπορούμε να αντλήσουμε δεδομένα χωρίς να το σκεφτούμε.

Βέλτιστες πρακτικές

  • Η ανάκτηση και η διαβίβαση δεδομένων από γονέα σε παιδί πρέπει να χρησιμοποιούνται με φειδώ.
  • Χρησιμοποιήστε βιβλιοθήκες όπως το dataloader για να απαλλαγείτε από τα αιτήματα.
  • Έχετε υπόψη σας οποιαδήποτε πίεση προκαλείτε στις πηγές δεδομένων σας.
  • Μη μεταλλάξετε το "πλαίσιο". Εξασφαλίζει συνεπή, λιγότερο buggy κώδικα.
  • Γράψτε αναλυτές που είναι αναγνώσιμοι, διατηρητέοι, δοκιμαστικοί. Δεν είναι πολύ έξυπνο.
  • Κάντε τους διαχωριστές σας όσο το δυνατόν λεπτότεροι. Εξαγάγετε τα δεδομένα που συλλέγουν τη λογική στις επαναχρησιμοποιούμενες συναρτήσεις συναγερμού.

Μείνετε συντονισμένοι!

Σκέψεις; Θα θέλαμε να ακούσουμε τις βέλτιστες πρακτικές και τις γνώσεις της ομάδας σας με τους μηχανισμούς επίλυσης προβλημάτων. Αυτό είναι ένα θέμα που δεν συζητείται συχνά, αλλά είναι σημαντικό για την οικοδόμηση API GraphQL AP.

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

Προσλαμβάνουμε! Εάν θέλετε να έρθετε σε υποδομή front-end, GraphQL ή React στο PayPal, DM μου στο Twitter στο @mark_stuart!