Vraag een software ontwikkelaar wat hij over web development weet en je zult hoogstwaarschijnlijk ergens in zijn antwoord de afkorting REST voorbij horen komen. In een tijd waarin een API gedreven architectuur de norm is, nemen RESTful webservices een zeer belangrijke plaats in. Sinds kort zijn er partijen die van REST afstappen en hun hoop vestigen op een volledig nieuwe manier van API ontwikkeling. Een framework dat is ontwikkeld door Facebook bestaande uit een querytaal en een runtime met de naam GraphQL. Dit alles met als doel om de data uitwisseling tussen API’s en front-end applicaties nog een stukje eenvoudiger, efficiënter en flexibeler te maken. Wat GraphQL is, hoe je het toepast en waarom je het in plaats van RESTful webservices wilt gebruiken, zal ik je in dit artikel uitleggen.
Huidige situatie
We leven in het tijdperk van REST API’s. Wie een web API bouwt, kiest er vaak bijna blind voor om hem met behulp van RESTful webservices met de buitenwereld te verbinden. Dit is een logische keuze, aangezien de principes van REST zowel front-end als backend engineers in staat stelt om op snelle en intuïtieve wijze web applicaties te ontwikkelen.
RESTful webservices hebben echter een groot nadeel: de API bepaalt exact welke data, in welke combinaties en in welk formaat de front-end applicaties kunnen ophalen. Hier zal je met één front-end applicatie weinig last van hebben, maar heb je een API die door meerdere front-ends wordt gebruikt (mobiele apps voor verschillende apparaten, websites, smart-tv apps, etc.) dan zal dit een probleem gaan vormen. Iedere front-end applicatie zal namelijk zijn eigen data behoefte hebben (die ook nog eens regelmatig zal wijzigen) waar de API ontwikkelaar rekening mee moet houden.
Dit kan inhouden dat je front-end applicaties meerdere calls naar de API moeten doen om de gewenste data op te halen. Hierbij wordt ook nog vaak allerlei data opgehaald die niet benut wordt. Dit komt de performance uiteraard niet ten goede. Om dit te voorkomen zou je API endpoints kunnen bouwen die exact aansluiten op de databehoeften van specifieke front-end applicaties. Dit heeft echter weer als nadeel dat het zorgt voor tight coupling tussen de API en je front-end applicatie. Kortom: het efficiënt managen van de relatie tussen een API en je front-end applicaties kan vandaag de dag erg ingewikkeld worden.
Dit probleem ervoer ook Facebook toen zij in 2011 hun gehele applicatielandschap wilden ombouwen naar een API gedreven architectuur. Met een website en een grote hoeveelheid apps voor zo’n beetje alle mobiele platformen liepen zij tegen de grenzen van RESTful webservices aan. Zij besloten daarom een nieuw framework te ontwikkelen. Een framework met de naam GraphQL met alle voordelen van een RESTful webservice, maar zonder zijn zwaktes.
Wat is GraphQL
GraphQL is een querytaal en runtime omgeving die je, net als REST, in staat stelt om een applicatie met de buitenwereld te verbinden. Met GraphQL kan een gebruiker, bijvoorbeeld een front-end applicatie, verzoeken versturen naar de backend applicatie. Met behulp van zelf samen te stellen queries kan de gebruiker hierin exact duidelijk maken wat hij van de API nodig heeft. De runtime van GraphQL, die meedraait in de backend applicatie, vertaalt deze queries naar functies die data kunnen ophalen of aanpassen. Hierin zie je ook meteen het grootste verschil met een RESTful webservice. Waar RESTful webservices over het algemeen meerdere endpoints aanbieden voor het ophalen en wijzigen van data, heeft een GraphQL API er slechts één. Dit maakt het mogelijk om met een enkel verzoek exact de data op te halen die je nodig hebt. Niet meer en niet minder! Om te kunnen begrijpen hoe de querytaal van GraphQL in elkaar zit, is het van belang om te kijken naar het belangrijkste onderdeel van een GraphQL gedreven API: het schema.
Het GraphQL schema
Het hart van een GraphQL API is het GraphQL schema. Met dit schema definieer je welke queries er allemaal mogelijk zijn op jouw API. Zo leg je in dit schema onder andere alle objecten en velden vast die de gebruiker mag opvragen, inclusief alle relaties tussen deze objecten. Ieder object dat je definieert in je schema moet worden gekoppeld aan een zelfgeschreven functie in jouw systeem. Deze functie is er verantwoordelijk voor om het gekozen object ook daadwerkelijk op te halen. Stel dat je een GraphQL API hebt met daarin een schema met boeken, schrijvers en uitgevers. Als ik als gebruiker alle boektitels inclusief schrijvers wil ophalen, kan ik dat met de volgende query doen:
books {
title,
writer { name }
}
In dit geval zal eerst voor het ‘books’ object een functie worden uitgevoerd waarin alle boeken worden opgehaald. Hierna zal bij ieder boek voor het ‘writer’ object een functie worden uitgevoerd waarin de bijbehorende schrijver wordt opgehaald. De GraphQL runtime voegt deze data samen, haalt er de velden uit die niet gevraagd zijn en stuurt de data in JSON formaat terug naar de gebruiker. Waar de query functies hun data vandaan halen, is voor GraphQL niet relevant. Dit is volledig aan de ontwikkelaar van de applicatie. De data kan uit een database komen, een interne API of bijvoorbeeld een cache.
Opbouw van een GraphQL schema
Een API waarmee je enkel data kunt ophalen is niet altijd toereikend. Je wilt soms ook data kunnen toevoegen, wijzigen en verwijderen. Ook dit is mogelijk met GraphQL en wel in de vorm van mutaties. Als je naar een GraphQL schema kijkt, is dat op te delen in twee specifieke takken. De eerste tak bevat de queries met daarin het pad waarlangs alle data in het schema kan worden opgehaald. De tweede tak bevat alle mutaties die op de data in het schema kunnen worden uitgevoerd. Een mutatie in GraphQL is simpelweg een functie die je aanroept met behulp van een GraphQL query. Hierin geef je enkel de naam van de mutatie inclusief de mogelijke parameters mee. De naam van de mutatie is gelijk aan de actie die de mutatie uitvoert. Laten we het voorbeeld van de boeken API er nog een keer bij pakken. Stel dat we een boek willen toevoegen in de applicatie. We kunnen hiervoor een mutatie definiëren met de naam ‘addBook’ met daarin een parameter voor de titel. De mutatie zou dan als volgt kunnen worden beschreven:
Mutation {
addBook (input: {
title: ‘booktitle’
})
}
GraphQL queries
Vanuit het oogpunt van de front-end developer draait GraphQL volledig om het afvuren van queries op de GraphQL API. De API bevat één endpoint waar de front-end developer met behulp van een HTTP POST zijn query in String formaat heen kan sturen. In deze paragraaf zal ik een aantal van de belangrijkste eigenschappen van GraphQL queries toelichten.
Laten we eerst nog een keer kijken naar de query voor het ophalen van alle boeken en bijbehorende schrijvers:
books {
title,
writer { name }
}
Deze query zou het volgende resultaat kunnen retourneren:
“books” : [
{ “title”: “Moby Dick”, “writer”: { “name”: “Herman Melville” } },
{ “title”: “Hamlet”, “writer”: { “name”: “William Shakespeare” } }
]
Wat je hier ziet, is dat de structuur van de query en het JSON resultaat bijna identiek zijn. Dit is bij alle queries die je uitvoert op een GraphQL API zo. Dit zorgt ervoor dat je als front-end developer altijd exact weet hoe de data die je terugkrijgt eruit ziet zonder dat je hier documentatie bij nodig hebt. Onderdeel hiervan is dat je in je query ook altijd exact moet definiëren welke velden je terug verwacht. Een query zoals hieronder zal dan ook een fout retourneren:
books {
writer{}
}
Het is in GraphQL ook mogelijk om argumenten toe te voegen aan je query om specifieker data te kunnen selecteren. Zo kan een gebruiker met onderstaande query bijvoorbeeld de naam van een specifiek boek ophalen:
query { book(id: 1) {title} }
Dit zou het volgende resultaat kunnen geven:
“book” {
“title”: “Moby Dick”
}
Deze argumenten zijn, net als de rest van het GraphQL schema, onderdelen die in de backend zelf moeten worden geïmplementeerd. GraphQL zorgt in de API enkel voor de vertaling van de query met bijbehorende parameters naar de functies die de data daadwerkelijk ophalen. Desondanks geeft het implementeren van argumenten je de mogelijkheid om op relatief eenvoudige wijze filtering, pagination en authorisatie in te bouwen in je API.
Strongtyping
Een belangrijke eigenschap van het GraphQL schema is dat het volledig strongtyped is. GraphQL heeft meerdere standaard types (integers, strings, booleans etc.) waarmee de velden in het schema van types kunnen worden voorzien. Objecten die je definieert, krijgen een zelf gedefinieerd type waarin de velden van het object zijn vastgelegd. Strongtyping zorgt ervoor dat de front-end developer altijd exact weet welke data, in welk formaat, hij terug kan verwachten wanneer hij een verzoek naar de GraphQL API stuurt. Daarnaast weet hij ook exact welke input de API bij een mutatie verwacht. Als we bijvoorbeeld de mutatie voor het aanmaken van een boek op onderstaande manier versturen, zal GraphQL automatisch een foutmelding retourneren.
Mutation {
addBook (input: {
title: 123
})
}
GraphQL voor de backend developer
GraphQL is beschikbaar in een groot aantal programmeertalen waaronder in Javascript, Java, C#, Python en PHP. De eenvoudigste manier om een Java API met GraphQL te ontwikkelen, is door gebruik te maken van de GraphQL Spring Boot starter. Hiermee heb je direct een volledig functionerende API met het GraphQL endpoint waar de gebruiker zijn queries heen kan sturen. Het enige wat je zelf moet doen, is het vullen van het GraphQL schema.
Hiervoor kun je gebruik maken van het volledig annotatie gedreven systeem dat in GraphQL voor Java is ingebouwd. De annotaties stellen je in staat om objecten, velden en mutaties te definiëren waarna ze automatisch worden opgenomen in het schema. In listing 1 zie je een voorbeeld van een eenvoudig GraphQL schema opgebouwd met annotaties. Je ziet dus dat je met erg weinig daadwerkelijke code een volledige GraphQL API kunt opzetten.
Listing 1
GraphiQL, geautomatiseerde documentatie
Een eigenschap van de GraphQL querytaal is dat je er niet enkel de inhoud van het GraphQL schema mee kunt uitvragen, maar ook de opbouw van het schema zelf. Dit houdt in dat je met de GraphQL querytaal bijvoorbeeld kunt ophalen welke objecten en velden het schema bevat. Maar bijvoorbeeld ook welke mutaties er allemaal mogelijk zijn en welke input ze verwachten. Deze eigenschap, introspectie genaamd, maakt het voor de gebruiker van de API zeer eenvoudig om de mogelijkheden van de API te ontdekken.
Als ondersteuning wordt met de Spring Boot variant van GraphQL standaard een tool meegeleverd waarmee het schema van de API in een web interface wordt weergegeven. Deze tool met de naam GraphiQL is een in-browser GraphQL IDE die draait naast je GraphQL server. GraphiQL geeft de gebruiker de mogelijkheid om GraphQL queries op de API te testen waarbij de gebruiker automatisch aanvul suggesties krijgt bij het invoeren van de query. Daarnaast laat GraphiQL je door alle objecten, velden en mutaties klikken die het schema bevat waarbij ook de types en mogelijke documentatie is in te zien.
GraphiQL verzorgt dus volledig geautomatiseerd de documentatie van jouw GraphQL API en maakt die op een centrale plaats beschikbaar voor jouw API gebruikers. Dit draagt uiteraard wederom bij aan het gebruikersgemak van de front-end developer. In Listing 2 zien je GraphiQL in actie.
Listing 2
Conclusie
GraphQL pakt de eenvoud en logica van RESTful webservices en voegt hier de nodige dosis autonomie en duidelijkheid voor de API gebruikers aan toe. Door de flexibele, intuïtieve querytaal, de eenvoudige backend implementatie en de uitstekende tooling is GraphQL dus een prima alternatief voor RESTful webservices.
Ter afsluiting kan ik slechts één ding zeggen: voor wie zijn front-end engineers een warm hart toedraagt, een API heeft met meerdere afnemers met verschillende wensen of simpelweg is uitgekeken op REST: probeer GraphQL eens uit. Je zult er geen spijt van krijgen!