Wat is GraphQL?

Overzicht

GraphQL is een nieuwe en opwindende API voor ad hoc-zoekopdrachten en -manipulatie. Het is extreem flexibel en biedt veel voordelen. Het is met name geschikt voor het blootstellen van gegevens die zijn georganiseerd als grafieken en bomen. Facebook heeft GraphQL in 2012 ontwikkeld en in 2015 open source aangeboden. 

Het begon snel en werd een van de heetste technologieën. Veel innovatieve bedrijven hebben GraphQL in productie genomen en gebruikt. In deze tutorial leer je: 

  • de principes van GraphQL
  • hoe het zich verhoudt tot REST
  • hoe schema's te ontwerpen
  • hoe een GraphQL-server op te zetten
  • hoe query's en mutaties moeten worden geïmplementeerd 
  • en een paar extra geavanceerde onderwerpen

Waar glint GraphQL?

GraphQL is op zijn best wanneer uw gegevens zijn geordend in een hiërarchie of een grafiek en de voorkant zou toegang willen hebben tot verschillende subsets van deze hiërarchie of grafiek. Overweeg een toepassing die de NBA blootlegt. Je hebt teams, spelers, coaches, kampioenschappen en veel informatie over elk kampioenschap. Hier zijn enkele voorbeeldvragen:

  • Wat zijn de namen van de spelers op het huidige rooster van de Golden State Warriors?
  • Wat zijn de namen, hoogten en leeftijden van de voorgerechten van de Washington Wizards?
  • Welke actieve coach heeft de meeste kampioenschappen?
  • Voor welke teams en in welke jaren heeft de coach zijn kampioenschappen gewonnen?
  • Welke speler heeft de meeste MVP-prijzen gewonnen?

Ik zou honderden van dergelijke vragen kunnen bedenken. Stel u voor dat u een API moet ontwerpen om al deze query's aan de voorkant te tonen en de API gemakkelijk kunt uitbreiden met nieuwe querytypen terwijl uw gebruikers of productmanager nieuwe spannende dingen bedenken om te zoeken.

Dit is niet triviaal. GraphQL is ontworpen om dit exacte probleem aan te pakken, en met een enkel API-eindpunt biedt het enorme kracht, zoals je snel zult zien.

GraphQL versus REST

Voordat we duiken in de moeren en bouten van GraphQL, laten we het vergelijken met REST, dat momenteel het populairste type web-API is.

REST volgt een resource-georiënteerd model. Als onze middelen spelers, coaches en teams zijn, dan zijn er waarschijnlijk eindpunten zoals:

  • / spelers 
  • / Spelers / 
  • / coaches
  • / Coaches / 
  • / teams
  • / Teams /

Vaak retourneren de eindpunten zonder ID gewoon een lijst met id's en retourneren de eindpunten met de id de volledige informatie over één resource. Je kunt je API natuurlijk ook op andere manieren ontwerpen (bijvoorbeeld het eindpunt / spelers kan ook de naam van elke speler of alle informatie over elke speler retourneren).

Het probleem met deze aanpak in een dynamische omgeving is dat je te kort komt (je krijgt bijvoorbeeld alleen de ID's en hebt meer informatie nodig) of je haalt te veel op (bijvoorbeeld de volledige informatie over elke speler krijgen als je gewoon geïnteresseerd in de naam). 

Dat zijn moeilijke problemen. Wanneer u te veel ophaalt, als u 100 id's ophaalt, moet u 100 afzonderlijke API-aanroepen uitvoeren om de informatie over elke speler te krijgen. Bij het overhalen verspilt u veel back-endtijd en netwerkbandbreedte die veel gegevens voorbereiden en overbrengen die niet nodig zijn.

Er zijn manieren om dit aan te pakken met REST. U kunt veel op maat gemaakte eindpunten ontwerpen, waarbij elke end exact de gegevens retourneert die u nodig hebt. Deze oplossing is niet schaalbaar. Het is moeilijk om de API consistent te houden. Het is moeilijk om het te evolueren. Het is moeilijk om het te documenteren en te gebruiken. Het is moeilijk om het te behouden wanneer er veel overlap is tussen die op maat gemaakte eindpunten.

Overweeg deze extra eindpunten:

  • / Spelers / namen
  • / Spelers / names_and_championships
  • / Team / starters

Een andere benadering is om een ​​klein aantal generieke eindpunten te behouden, maar veel queryparameters te bieden. Deze oplossing vermijdt het vele probleem met eindpunten, maar het druist in tegen het REST-model en het is ook moeilijk om consequent te evolueren en te onderhouden.

Je zou kunnen zeggen dat GraphQL deze benadering tot het uiterste heeft genomen. Het denkt niet in termen van goed gedefinieerde bronnen, maar in termen van subgrafieken van het hele domein.

Het GraphQL-type systeem

GraphQL modelleert het domein met behulp van een type systeem dat bestaat uit typen en attributen. Elk attribuut heeft een type. Het kenmerktype kan een van de basistypen zijn die GraphQL biedt, zoals ID, String en Boolean, of een door de gebruiker gedefinieerd type. De knooppunten van de grafiek zijn de door de gebruiker gedefinieerde typen en de randen zijn de kenmerken met door de gebruiker gedefinieerde typen. 

Als een "Player" -type bijvoorbeeld een "team" -attribuut met het "Team" -type heeft, betekent dit dat er een rand is tussen elk spelersknooppunt naar een teamknooppunt. Alle typen worden gedefinieerd in een schema dat het GraphQL-domeinobjectmodel beschrijft. 

Hier is een zeer vereenvoudigd schema voor het NBA-domein. De speler heeft een naam, een team waarmee hij het meest geassocieerd is (ja, ik weet dat spelers soms van het ene team naar het andere gaan) en het aantal kampioenschappen dat de speler heeft gewonnen. 

Het team heeft een naam, een reeks spelers en het aantal kampioenschappen dat het team heeft gewonnen.

typ Speler id: ID-naam: String! team: Team! championshipCount: Integer!  typ Team id: ID-naam: String! spelers: [Speler!]! championshipCount: Integer!  

Er zijn ook vooraf gedefinieerde toegangspunten. Dat zijn query's, mutaties en abonnementen. De voorkant communiceert met de achterkant via de ingangspunten en past deze aan aan zijn behoeften.

Hier is een query die eenvoudigweg alle spelers retourneert:

type Query allPlayers: [Player!]! 

Het uitroepteken betekent dat de waarde niet nul kan zijn. In het geval van de allPlayers query, kan deze een lege lijst retourneren, maar niet null. Het betekent ook dat er geen nul-speler in de lijst kan staan ​​(omdat het Player bevat!).

Een GraphQL-server opzetten

Hier is een volwaardige GraphQL-server op basis van node-express. Het heeft een hard-gecodeerde gegevensopslag in het geheugen. Normaal gesproken staan ​​de gegevens in een database of worden ze opgehaald bij een andere service. De gegevens worden hier gedefinieerd (excuses op voorhand als je favoriete team of speler het niet heeft gehaald):

laat data = "allPlayers": "1": "id": "1", "naam": "Stephen Curry", "championshipCount": 2, "teamId": "3", "2": "id": "2", "name": "Michael Jordan", "championshipCount": 6, "teamId": "1", "3": "id": "3", "name": "Scottie Pippen", "championshipCount": 6, "teamId": "1", "4": "id": "4", "name": "Magic Johnson", "championshipCount": 5, "teamId ":" 2 "," 5 ": " id ":" 5 "," name ":" Kobe Bryant "," championshipCount ": 5," teamId ":" 2 "," 6 ": " id ":" 6 "," name ":" Kevin Durant "," championshipCount ": 1," teamId ":" 3 "," allTeams ": " 1 ": " id ":" 1 ", "naam": "Chicago Bulls", "championshipCount": 6, "spelers": [], "2": "id": "2", "naam": "Los Angeles Lakers", "championshipCount": 16, "spelers": [], "3": "id": "3", "naam": "Golden State Warriors", "championshipCount": 5, "spelers": [] 

De bibliotheken die ik gebruik zijn:

const express = vereisen ('express'); const graphqlHTTP = require ('express-graphql'); const app = express (); const buildSchema = require ('graphql'); const _ = vereisen ('lodash / core');

Dit is de code om het schema te bouwen. Merk op dat ik een paar variabelen aan de allPlayers root-query.

schema = buildSchema ('type Speler id: ID naam: String! championshipCount: Int! team: Team! type Team id: ID naam: String! championshipCount: Int! spelers: [Player!]! type Query allPlayers (offset: Int = 0, limit: Int = -1): [Player!]! ' 

Hier komt het belangrijkste deel: het aansluiten van de vragen en het feitelijk bedienen van de gegevens. De rootValue object kan meerdere wortels bevatten. 

Hier is alleen de allPlayers. Het haalt de offset en limiet uit de argumenten, verdeelt de gegevens van alle spelers en stelt vervolgens het team op elke speler in op basis van het team-ID. Hierdoor is elke speler een genest object.

rootValue = allPlayers: (args) => offset = args ['offset'] limit = args ['limit'] r = _.values ​​(data ["allPlayers"]). slice (offset) if (limiet> - 1) r = r.slice (0, Math.min (limit, r.length)) _.forEach (r, (x) => data.allPlayers [x.id] .team = data.allTeams [ x.teamId]) return r, 

Eindelijk, hier is de graphql eindpunt, het schema en het rootwaarde-object passeren:

app.use ('/ graphql', graphqlHTTP (schema: rootValue: rootValue, graphiql: true)); app.listen (3000); module.exports = app;

omgeving graphiql naar waar stelt ons in staat om de server te testen met een geweldige in-browser GraphQL IDE. Ik raadde het ten zeerste aan om te experimenteren met verschillende vragen.

Ad-hocquery's met GraphQL

Alles is ingesteld. Laten we naar http: // localhost: 3000 / graphql navigeren en wat plezier hebben.

We kunnen eenvoudig beginnen, met slechts een lijst met de spelersnamen:

query justNames allPlayers name Output: "data": "allPlayers": ["name": "Stephen Curry", "name": "Michael Jordan", "name": "Scottie Pippen ", " name ":" Magic Johnson ", " name ":" Kobe Bryant ", " name ":" Kevin Durant "] 

Alright. We hebben hier een aantal supersterren. Ongetwijfeld. Laten we iets mooiers gaan doen: vanaf offset 4 krijg je 2 spelers. Geef voor elke speler zijn naam terug en hoeveel kampioenschappen ze hebben gewonnen, evenals hun teamnaam en hoeveel kampioenschappen het team heeft gewonnen.

query twoPlayers allPlayers (offset: 4, limit: 2) naam championshipCount team naam championshipCount Uitgang: "data": "allPlayers": ["name": "Kobe Bryant", "championshipCount": 5, "team": "naam": "Los Angeles Lakers", "championshipCount": 16, "naam": "Kevin Durant", "championshipCount": 1, "team": "naam": "Golden State Warriors", "championshipCount": 5] 

Dus Kobe Bryant won vijf kampioenschappen met de Lakers, die in totaal 16 kampioenschappen wonnen. Kevin Durant won slechts één kampioenschap met de Warriors, die in totaal vijf kampioenschappen wonnen.

GraphQL-mutaties

Magic Johnson was zeker een goochelaar op het veld. Maar hij had het niet kunnen doen zonder zijn maat Kareem Abdul-Jabbar. Laten we Kareem toevoegen aan onze database. We kunnen GraphQL-mutaties definiëren om bewerkingen uit te voeren, zoals het toevoegen, bijwerken en verwijderen van gegevens uit onze grafiek.

Laten we eerst een mutatietype toevoegen aan het schema. Het lijkt een beetje op een handtekening van een functie:

type Mutatie createPlayer (naam: String, championshipCount: Int, teamId: String): Player

Vervolgens moeten we het implementeren en toevoegen aan de rootwaarde. De implementatie neemt eenvoudig de parameters van de query en voegt een nieuw object toe aan de data [ 'allPlayers']. Het zorgt er ook voor dat het team correct wordt ingesteld. Ten slotte geeft het de nieuwe speler terug.

 createPlayer: (args) => id = (_.values ​​(data ['allPlayers']). length + 1) .toString () args ['id'] = id args ['team'] = data ['allTeams '] [args [' teamId ']] data [' allPlayers '] [id] = args return-gegevens [' allPlayers '] [id],

Om Kareem daadwerkelijk toe te voegen, kunnen we de mutatie oproepen en de geretourneerde speler ondervragen:

mutatie addKareem createPlayer (naam: "Kareem Abdul-Jabbar", championshipCount: 6, teamId: "2") naam championshipCount team name Uitgang: "data": "createPlayer": "name": "Kareem Abdul-Jabbar", "championshipCount": 6, "team": "name": "Los Angeles Lakers" 

Hier is een duister klein geheim over mutaties ... ze zijn eigenlijk precies hetzelfde als vragen. U kunt uw gegevens in een query aanpassen en u kunt alleen gegevens van een mutatie retourneren. GraphQL zal je code niet zien. Zowel query's als mutaties kunnen argumenten aannemen en gegevens retourneren. Het is meer syntactische suiker om je schema menselijker leesbaar te maken.

Geavanceerde onderwerpen

abonnementen

Abonnementen zijn nog een geweldige feature van GraphQL. Met abonnementen kan de client zich abonneren op gebeurtenissen die worden geactiveerd wanneer de serverstatus verandert. Abonnementen zijn in een later stadium geïntroduceerd en worden op verschillende manieren geïmplementeerd door verschillende frameworks.

bevestiging

GraphQL verifieert elke query of mutatie ten opzichte van het schema. Dit is een grote overwinning als de invoergegevens een complexe vorm hebben. U hoeft geen vervelende en broze validatiecode te schrijven. GraphQL zal het voor je regelen. 

Schema Introspectie

U kunt het huidige schema zelf bekijken en bevragen. Dat geeft je meta-krachten om het schema dynamisch te ontdekken. Hier is een query die alle typenamen en hun beschrijving retourneert:

query q __schema types name description

Conclusie

GraphQL is een opwindende nieuwe API-technologie die veel voordelen biedt ten opzichte van REST-API's. Er zit een levendige gemeenschap achter, om nog maar te zwijgen van Facebook. Ik voorspel dat het in een mum van tijd een front-end nietje zal worden. Probeer het eens. Je zal het leuk vinden.