Artikel uit SDN Magazine 148
Binnen .NET oplossingen wordt voor het maken van webservices heel vaak gebruik gemaakt van WebAPI. Toch is dit niet de enige manier. In eerdere edities van het SDN-magazine heb ik al laten zien hoe gRPC hier ook voor kan worden gebruikt. Binnen het .NET ecosysteem zijn er nog andere mogelijkheden. Een van die mogelijkheid is GraphQL. GraphQL speelt een steeds belangrijkere rol bij het maken van webservices. In dit artikel zal worden beschreven hoe een simpele GraphQL service met behulp van .NET kan worden gemaakt. Dit artikel is onderdeel van een reeks artikelen over GraphQL. In vervolgartikelen zullen andere aspecten aan de orde komen, zoals het muteren van data met behulp van GraphQL.
Waarom geen WebAPI
Voordat ingegaan wordt op de manier waarop met behulp van GraphQL een service gebouwd kan worden, is het belangrijk om te weten waarom GraphQL bedacht is. Dit zal aan de hand van een voorbeeld worden toegelicht. Voor dit artikel wordt uitgegaan van het volgende voorbeeld (Figuur 1).
Figuur 1 Domeinmodel van het voorbeeld
Het voorbeeld zal voor veel ontwikkelaars bekend zijn. Het is een deel van een order systeem binnen een website.
Als voor dit voorbeeld een simpele gebruikersinterface moet worden ontworpen, kan deze eruitzien als Figuur 2. Zoals vaak in systemen het geval is, wordt de informatie van meerdere entiteiten/klassen binnen een scherm getoond.
Figuur 2 Wireframe voor de gebruikersinterface
De gebruikersinterface voor dit voorbeeld zal waarschijnlijk als single page application (SPA) gerealiseerd worden. Voor het ophalen van de gegevens uit de backend is dus minimaal 1 webservice nodig. Als de webservice die de gegevens voor deze applicatie levert, gebaseerd wordt op Restful principes, dan moeten volgens het Richardson Maturity Model level 2 (https://martinfowler.com/articles/richardsonMaturityModel.html) de volgende resources aangeboden worden door deze service:
- Order
- Orderline
- Customer
- Product
Voor het ophalen van de gegevens voor het getoonde scherm, moeten dan de volgende endpoints binnen deze webservice aangeroepen worden:
- /order/1
- /order/1/orderline/1
- /order/1/orderline/2
- /order/1/orderline/1/product of via /product/1
- /order/1/orderline/2/product of via /product/2
- /order/1/customer of via /customer/1
Om alle gegevens op te kunnen halen die nodig zijn voor dit scherm moeten dus veel endpoints aan worden gesproken. Het aanroepen van de endpoints betekent dus ook dat er veel calls over het netwerk moeten worden gemaakt met alle latency van dien.
Binnen een Restful aanpak is het gebruikelijk om standaard alle attributen/property’s van een resource terug te geven. Voor het scherm zijn echter niet alle attributen nodig. Door de Restful aanpak worden dus niet alleen heel veel calls gemaakt, maar wordt er per call ook veel onnodige informatie ‘over-the-wire’ gestuurd.
Vanwege deze nadelen heeft Facebook (Meta) in 2012 GraphQL bedacht als querytaal voor API’s om ervoor te zorgen dat op een eenvoudige manier specifieke data uit één backend service kan worden gehaald. GraphQL vervult dus een beetje dezelfde functie voor API’s als SQL voor relationele databases. Om deze data op te kunnen vragen, moet een GraphQL server wel een schema publiceren, zodat een client weer welke data opgevraagd kan worden. Ook dit is weer vergelijkbaar met de meta informatie die een relationele database heeft over de tabellen en andere databaseonderdelen.
Met behulp van GraphQL kan via de volgende query alle benodigde informatie op worden gehaald die nodig is voor het scherm (Figuur 3).
Figuur 3 GraphQL Query voor informatie op scherm
Binnen de query moet aangegeven worden dat er sprake is van een query, omdat ook aanpassingen met GraphQL kunnen worden uitgevoerd. Binnen de query moeten de velden worden gespecificeerd die teruggegeven moeten worden. Velden van gerelateerde entiteiten kunnen via de naam van de relatie op worden gehaald. De syntax van GraphQL is dus relatief simpel vergeleken met SQL. Meer informatie over de GraphQL syntax staat op de site van de GraphQL Foundation (https://graphql.org/). Deze organisatie is tegenwoordig verantwoordelijk voor GraphQL.
Het gebruik van GraphQL kan dus de genoemde nadelen van Restful tegengaan.
GraphQL binnen .NET
GraphQL biedt dus zeker voordelen ten opzichte van de standaard Restful WebAPI’s die nu vaak binnen .NET worden gebruikt. Maar hoe kun je nu als .NET developer zelf je eigen GraphQL-services maken? Standaard biedt Microsoft op dit moment nog geen ondersteuning voor GraphQL. Dit wil niet zeggen dat je nog geen services kunt maken, want gelukkig biedt de .NET gemeenschap een aantal zeer goed bruikbare NuGet packages aan om dit wel mogelijk te maken. Voor dit artikel wordt gebruik gemaakt van HotChocolate (https://chillicream.com/docs/hotchocolate/v13).
De basis voor het maken van een GraphQL-service is ASP.NET Core. Hierbij kan worden gekozen voor de “Empty” template. Voor dit artikel is echter uitgegaan van de template voor ASP.NET WebAPI om ook aan te tonen dat een GraphQL service ook naast een Restful service kan bestaan. Of dit wenselijk is in het kader van separation of concerns laat ik even in het midden.
Om met HotChocolate aan de slag te gaan, moet de volgende NuGet-package aan het project toe worden gevoegd: “HotChocolate.AspNetCore” (Figuur 4). De code die de basis is van dit artikel maakt gebruikt van versie 13.4 van deze package.
Figuur 4 NuGet package voor HotChocolate
HotChocolate maakt het mogelijk om de servercode te maken door middel van annotaties, code of op basis van een schema. Omdat voor ontwikkelaars de code-first aanpak waarschijnlijk het meeste aanspreekt, wordt in dit artikel voor deze aanpak gekozen.
Domein klassen
Binnen het API-project kan de code voor het domeinmodel opgenomen worden. Eventueel kan dit natuurlijk ook in een losse class library ondergebracht worden.
Figuur 5 Domeinklassen in project
Binnen de domainklassen hoef je als ontwikkelaar geen rekening te houden met GraphQL (Figuren 6-10). De code houdt al wel rekening met gebruik binnen Entity Framework.
Figuur 6 Code Order
Figuur 7 Code OrderLine
Figuur 8 Code OrderStatus
Figuur 9 Code Product
Figuur 10 Code Customer
HotChocolate specifieke code
Nu de domeincode klaar is, moet een klasse worden gemaakt die verantwoordelijk is voor het afvangen van de verzoeken voor het opvragen van de gegevens. Binnen HotChocolate wordt hiervoor een Query klasse gebruikt. Deze klasse hoeft niet te erven van een speciale baseclass (Figuur 11).
Figuur 11 OrderQuery
Binnen deze klasse moeten de types gedeclareerd worden die beschikbaar moeten zijn binnen GraphQL. Hiermee wordt dus het schema bepaald.
Figuur 12 GetAllOrders
Binnen het uiteindelijke schema wordt Get niet meegenomen. Voor het schema heet dit type dus AllOrders.
De GenerateTestOrders maakt een lijst met Orders aan en onderliggende objecten aan. Normaal zou deze informatie natuurlijk uit een database komen, maar om dit voorbeeld simpel te houden, is gekozen voor een lijst in het geheugen.
Figuur 13 Code GenerateTestOrders
Deze Query klasse moet natuurlijk nog wel beschikbaar gesteld worden voor clients op de een of andere manier. Hiervoor moeten nog aanpassingen in Program.cs worden gemaakt.
Allereerst moet de GraphQLServer als Service toe worden gevoegd. Bovendien moet de Query klasse die net geschreven is, als QueryType aan de server toe worden gevoegd (Figuur 14).
Figuur 14 Aanpassingen Services en toevoegen Query
De laatste aanpassing die moet worden gemaakt, is het mappen van de GraphQL endpoint binnen de service (Figuur 15).
Figuur 15 Map endpoint
Dit is eigenlijk alle code die we nodig hebben om de server nu operationeel te maken.
Testen van de GraphQL service
Binnen WebAPI wordt over het algemeen een standaard testbed gebruikt in de vorm van Swagger. Ook voor HotChocolate is een standaard testbed beschikbaar in de vorm van Banana Cake Pop. Deze kan worden bereikt door achter de url “/graphql/’ te typen (Figuur 16).
Figuur 16 Banana Cake Pop
Het schema dat door HotChocolate is gemaakt, kan via de tabs “Schema Reference” en “Schema Definition” worden bekeken. De meest gebruikersvriendelijke weergave is hierbij de reference, omdat die de minste kennis van de GraphQL schemataal vraagt (Figuur 17).
Figuur 17 Schema OrderQuery
Zoals in Figuur 17 te zien is, is inderdaad de Get niet meegenomen in het schema. Het Order schema kan worden geraadpleegd door erop te klikken (Figuur 18).
Figuur 18 Order schema
De uitroeptekens geven aan dat relaties/attributen niet null mogen zijn.
Aan de hand van dit schema kan nu een query samen worden gesteld. Doordat er schemainformatie is, kan de omgeving ook hints geven (Figuur 19).
Figuur 19 Opstellen query met code completion
Op deze manier kan dus uiteindelijk de volgende query worden gemaakt (Figuur 20).
Figuur 20 allOrders Query
Als deze uitgevoerd wordt door op de Run-knop te drukken, dan worden de testorders opgehaald en getoond (Figuur 21).
Figuur 21 Query resultaat
Ophalen van één order
In het voorgaande voorbeeld is de hele lijst van orders via de service opgehaald. In de praktijk werkt dit natuurlijk niet, want je gaat niet alle orders uit een systeem via een service in 1 keer ophalen. Gelukkig kun je binnen GraphQL ook één enkele entiteit ophalen. Hiervoor moet gebruik worden gemaakt van parameters (Figuur 22).
Figuur 22 Code OrderById
In de query kun je dan de waarde van de id meegeven van de specifieke entiteit die je op wilt halen (Figuur 23). De mogelijkheid om de attributen op te geven die je terug wilt krijgen, blijft natuurlijk bestaan.
Figuur 23 Query voor het ophalen van 1 entiteit
Als resultaat van de query komt nu maar 1 entiteit (met subentiteiten) terug (Figuur 24).
Figuur 24 Resultaat ophalen entiteit met parameter
Conclusie
Met behulp van HotChocolate kan op een eenvoudige manier een GraphQL server in .NET worden gemaakt. Dit is natuurlijk niet de enige mogelijkheid, maar ik vind het wel een van de simpelste. Natuurlijk heeft dit artikel alleen maar de top van de spreekwoordelijke ijsberg laten zien. In een volgend artikel zal de integratie met Entity Framework nog behandeld worden. Omdat GraphQL ook gebruikt kan worden om wijzigingen door te geven, zal in een ander vervolgartikel dit aspect ook beschreven worden. Omdat we natuurlijk ook nog graag GraphQL services kunnen consumeren zal hieraan ook nog een artikel worden gewijd.
Op https://github.com/johansmarius staat de code die gebruikt is in dit artikel. De screenshots in dit artikel zijn afkomstig uit deze code.