Aan de slag met GraphQL en Spring Boot

1. Inleiding

GraphQL is een relatief nieuw concept van Facebook dat wordt gefactureerd als alternatief voor REST voor web-API's.

Dit artikel geeft een inleiding tot het opzetten van een GraphQL-server met Spring Boot, zodat deze kan worden toegevoegd aan bestaande applicaties of kan worden gebruikt in nieuwe.

2. Wat is GraphQL?

Traditionele REST API's werken met het concept van bronnen die de server beheert. Deze bronnen kunnen op een aantal standaardmanieren worden gemanipuleerd, waarbij de verschillende HTTP-werkwoorden worden gevolgd. Dit werkt erg goed zolang onze API past bij het resource-concept, maar valt snel uit elkaar als we hiervan moeten afwijken.

Dit lijdt ook wanneer de klant tegelijkertijd gegevens uit meerdere bronnen nodig heeft. Bijvoorbeeld het aanvragen van een blogpost en de reacties. Meestal wordt dit opgelost door de client meerdere verzoeken te laten doen of door de server extra gegevens te laten leveren die niet altijd nodig zijn, wat leidt tot grotere responsgroottes.

GraphQL biedt een oplossing voor beide problemen. Het stelt de klant in staat om precies te specificeren welke gegevens gewenst zijn, inclusief het navigeren door onderliggende bronnen in één enkel verzoek, en maakt meerdere zoekopdrachten in één enkel verzoek mogelijk.

Het werkt ook op een veel meer RPC-manier, met benoemde queries en mutaties in plaats van een standaard verplichte set acties. Dit werkt om de controle te plaatsen waar het thuishoort, waarbij de API-ontwikkelaar specificeert wat mogelijk is en de API-consument wat gewenst is.

Een blog kan bijvoorbeeld de volgende zoekopdracht toestaan:

zoekopdracht {recentPosts (count: 10, offset: 0) {id titel categorie auteur {id naam thumbnail}}}

Deze vraag zal:

  • vraag de tien meest recente berichten aan
  • Vraag voor elk bericht de ID, titel en categorie aan
  • vraag voor elk bericht de auteur, waarbij de ID, naam en miniatuur wordt geretourneerd

In een traditionele REST API heeft dit ofwel 11 verzoeken nodig - 1 voor de posts en 10 voor de auteurs - of moet de auteur details opnemen in de post details.

2.1. GraphQL-schema's

De GraphQL-server legt een schema bloot dat de API beschrijft. Dit schema is opgebouwd uit typedefinities. Elk type heeft een of meer velden, die elk nul of meer argumenten hebben en een specifiek type retourneren.

De grafiek is opgebouwd uit de manier waarop deze velden in elkaar zijn genest. Merk op dat de grafiek niet acyclisch hoeft te zijn - cycli zijn volkomen acceptabel - maar hij is wel gericht. Dat wil zeggen dat de client van het ene veld naar de onderliggende velden kan gaan, maar hij kan niet automatisch terugkeren naar de bovenliggende positie, tenzij het schema dit expliciet definieert.

Een voorbeeld van een GraphQL-schema voor een blog kan de volgende definities bevatten: een bericht, een auteur van het bericht en een rootquery om de meest recente berichten op de blog te krijgen.

typ Post {id: ID! title: String! tekst reeks! categorie: String auteur: Auteur! } typ Auteur {id: ID! naam: String! thumbnail: String berichten: [Post]! } # De rootquery voor het applicatietype Query {recentPosts (count: Int, offset: Int): [Post]! } # De root-mutatie voor het applicatietype Mutation {writePost (titel: String !, tekst: String !, categorie: String): Post! }

De "!" aan het einde van sommige namen geeft aan dat dit een niet-nullabel type is. Elk type dat dit niet heeft, kan null zijn in het antwoord van de server. De GraphQL-service verwerkt deze correct, waardoor we kindvelden van nullable-typen veilig kunnen opvragen.

De GraphQL-service stelt ook het schema zelf bloot met behulp van een standaardset van velden, waardoor elke klant van tevoren naar de schemadefinitie kan zoeken.

Hierdoor kan de client automatisch detecteren wanneer het schema verandert, en kunnen clients zich dynamisch aanpassen aan de manier waarop het schema werkt. Een ongelooflijk handig voorbeeld hiervan is de GraphiQL-tool - die later wordt besproken - waarmee we kunnen communiceren met elke GraphQL-API.

3. Introductie van GraphQL Spring Boot Starter

De Spring Boot GraphQL Starter biedt een fantastische manier om een ​​GraphQL-server in zeer korte tijd te laten draaien. In combinatie met de GraphQL Java Tools-bibliotheek hoeven we alleen de code te schrijven die nodig is voor onze service.

3.1. De service opzetten

Alles wat we nodig hebben om dit te laten werken, zijn de juiste afhankelijkheden:

 com.graphql-java graphql-spring-boot-starter 5.0.2 com.graphql-java graphql-java-tools 5.2.4 

Spring Boot pikt deze automatisch op en stelt de juiste handlers in om automatisch te werken.

Dit geeft standaard de GraphQL-service weer op het / graphql eindpunt van onze applicatie en accepteert POST-verzoeken die de GraphQL Payload bevatten. Dit eindpunt kan worden aangepast in ons application.properties bestand indien nodig.

3.2. Schema schrijven

De GraphQL Tools-bibliotheek werkt door GraphQL-schemabestanden te verwerken om de juiste structuur op te bouwen en vervolgens speciale bonen aan deze structuur te koppelen. De Spring Boot GraphQL-starter vindt deze schemabestanden automatisch.

Deze bestanden moeten worden opgeslagen met de extensie “.graphqls”En kan overal op het klassenpad aanwezig zijn. We kunnen ook zoveel van deze bestanden hebben als we willen, zodat we het schema naar wens kunnen opsplitsen in modules.

De enige vereiste is dat er precies één root-query moet zijn, en maximaal één root-mutatie. Dit kan, in tegenstelling tot de rest van het schema, niet over bestanden worden verdeeld. Dit is een beperking van de GraphQL-schemadefinitie zelf, en niet van de Java-implementatie.

3.3. Root Query Resolver

De rootquery heeft speciale bonen nodig die zijn gedefinieerd in de Spring-context om de verschillende velden in deze rootquery te kunnen afhandelen. In tegenstelling tot de schemadefinitie is er geen beperking dat er slechts één Spring Bean is voor de rootqueryvelden.

De enige vereisten zijn die de bonen implementeren GraphQLQueryResolver en dat elk veld in de root-query van het schema een methode heeft in een van deze klassen met dezelfde naam.

public class Query implementeert GraphQLQueryResolver {private Post Dao postão; openbare lijst getRecentPosts (int count, int offset) {retourneer posts Dao.getRecentPosts (count, offset); }}

De namen van de methode moeten een van de volgende zijn, in deze volgorde:

  1. is - alleen als het veld van het type is Boolean
  2. krijgen

De methode moet parameters hebben die overeenkomen met alle parameters in het GraphQL-schema en kan optioneel een laatste parameter van het type aannemen DataFetchingEnvironment.

De methode moet ook het juiste retourtype retourneren voor het type in het GraphQL-schema, zoals we gaan zien. Alle eenvoudige typen - String, Int, Lijst, enz. - kunnen worden gebruikt met de equivalente Java-typen, en het systeem wijst ze automatisch toe.

Het bovenstaande definieerde de methode getRecentPosts die zal worden gebruikt om GraphQL-query's voor het recente berichten veld in het eerder gedefinieerde schema.

3.4. Bonen gebruiken om soorten weer te geven

Elk complex type in de GraphQL-server wordt vertegenwoordigd door een Java-bean - of het nu is geladen vanuit de rootquery of ergens anders in de structuur. Dezelfde Java-klasse moet altijd hetzelfde GraphQL-type vertegenwoordigen, maar de naam van de klasse is niet nodig.

Velden in de Java-bean worden rechtstreeks toegewezen aan velden in het GraphQL-antwoord op basis van de naam van het veld.

openbare klasse Post {privé String-id; private String-titel; privé String-categorie; private String authorId; }

Alle velden of methoden op de Java-bean die niet overeenkomen met het GraphQL-schema, worden genegeerd, maar veroorzaken geen problemen. Dit is belangrijk voor veldresolvers om te werken.

Bijvoorbeeld het veld authorId komt hier niet overeen met iets in ons schema dat we eerder hebben gedefinieerd, maar het zal beschikbaar zijn voor gebruik voor de volgende stap.

3.5. Veldresolvers voor complexe waarden

Soms is de waarde van een veld niet triviaal om te laden. Dit kan gaan om het opzoeken van databases, complexe berekeningen of iets anders. GraphQL Tools heeft een concept van een veldresolver die voor dit doel wordt gebruikt. Dit zijn Spring Beans die waarden kunnen leveren in plaats van de data bean.

De veldresolver is elke bean in de Spring Context die dezelfde naam heeft als de data bean, met het achtervoegsel Resolver, en dat implementeert de GraphQLResolver koppel. Methoden op de field resolver bean volgen dezelfde regels als op de data bean, maar krijgen ook de data bean zelf als eerste parameter.

Als een veldresolver en de databoon beide methoden hebben voor hetzelfde GraphQL-veld, heeft de veldresolver voorrang.

public class PostResolver implementeert GraphQLResolver {private Authorbao authorensional; openbare auteur getAuthor (Post post) {return author Domain.getAuthorById (post.getAuthorId ()); }}

Belangrijk is dat deze veldresolvers worden geladen vanuit de Spring-context. Hierdoor kunnen ze werken met andere door Spring beheerde bonen, bijvoorbeeld DAO's.

Belangrijker nog, als de cliënt geen veld opvraagt, zal de GraphQL Server nooit het werk doen om het op te halen. Dit betekent dat als een klant een bericht ophaalt en niet naar de auteur vraagt, het getAuthor () bovenstaande methode zal nooit worden uitgevoerd en de DAO-aanroep zal nooit worden uitgevoerd.

3.6. Nullable waarden

Het GraphQL-schema heeft het concept dat sommige typen nullabel zijn en andere niet.

Dit kan in de Java-code worden afgehandeld door rechtstreeks null-waarden te gebruiken, maar evengoed de nieuwe Optioneel type uit Java 8 kan hier direct worden gebruikt voor nullable-typen, en het systeem zal het juiste doen met de waarden.

Dit is erg handig omdat het betekent dat onze Java-code duidelijker hetzelfde is als het GraphQL-schema uit de methodedefinities.

3.7. Mutaties

Alles wat we tot nu toe hebben gedaan, ging over het ophalen van gegevens van de server. GraphQL heeft ook de mogelijkheid om de gegevens die op de server zijn opgeslagen bij te werken door middel van mutaties.

Vanuit het oogpunt van de code is er geen reden dat een Query de gegevens op de server niet kan wijzigen. We kunnen gemakkelijk query-resolvers schrijven die argumenten accepteren, nieuwe gegevens opslaan en die wijzigingen retourneren. Als u dit doet, heeft dit verrassende bijwerkingen voor de API-clients en wordt dit als een slechte praktijk beschouwd.

In plaats daarvan, Mutaties moeten worden gebruikt om de klant te informeren dat dit een wijziging zal veroorzaken in de gegevens die worden opgeslagen.

Mutaties worden in de Java-code gedefinieerd door klassen te gebruiken die implementeren GraphQLMutationResolver in plaats van GraphQLQueryResolver.

Anders zijn dezelfde regels van toepassing als voor zoekopdrachten. De geretourneerde waarde van een mutatieveld wordt dan exact hetzelfde behandeld als van een zoekveld, waardoor ook geneste waarden kunnen worden opgehaald.

public class Mutation implementeert GraphQLMutationResolver {private Post Dao postão; public Post writePost (String-titel, String-tekst, String-categorie) {return postão.savePost (titel, tekst, categorie); }}

4. Introductie van GraphiQL

GraphQL heeft ook een begeleidende tool genaamd GraphiQL. Dit is een gebruikersinterface die kan communiceren met elke GraphQL-server en er query's en mutaties op kan uitvoeren. Een downloadbare versie ervan bestaat als een Electron-app en kan vanaf hier worden opgehaald.

Het is ook mogelijk om de webgebaseerde versie van GraphiQL automatisch in onze applicatie op te nemen, door de GraphiQL Spring Boot Starter-afhankelijkheid toe te voegen:

 com.graphql-java graphiql-spring-boot-starter 5.0.2 

Dit werkt alleen als we onze GraphQL API hosten op het standaard eindpunt van / graphql als dat niet het geval is, is de zelfstandige applicatie nodig.

5. Samenvatting

GraphQL is een zeer opwindende nieuwe technologie die mogelijk een revolutie teweeg kan brengen in de manier waarop web-API's worden ontwikkeld.

De combinatie van de Spring Boot GraphQL Starter en de GraphQL Java Tools-bibliotheken maakt het ongelooflijk eenvoudig om deze technologie toe te voegen aan nieuwe of bestaande Spring Boot-applicaties.

Codefragmenten zijn te vinden op GitHub.