Go en het OpenAPI-ecosysteem

Inleiding
Wanneer je een Production Backend-server ontwikkelt in Go, stuit bijna elke ontwikkelaar vrijwel als eerste op een van de volgende uitdagingen.
API-documentatie, hoe moet ik dat aanpakken...?
Na enig onderzoek wordt duidelijk dat het voordelig is om documentatie op te stellen die aansluit op de OpenAPI-specificatie, en vervolgens zoekt men vanzelfsprekend naar bibliotheken die met OpenAPI integreren. Maar ook nadat deze beslissing is genomen, rijst het volgende probleem.
Er zijn veel OpenAPI-gerelateerde bibliotheken... welke moet ik gebruiken...?
Dit document is een beknopte inleiding tot bibliotheken, geschreven voor beginnende Go-ontwikkelaars die deze situatie herkennen. Het is opgesteld per eind 2024, en aangezien het ecosysteem van een taal voortdurend verandert, raden wij aan dit document als referentie te gebruiken en tevens regelmatig de meest recente ontwikkelingen na te gaan.
De strategieën van bibliotheken ten aanzien van OpenAPI
Het is waarschijnlijk al bekend, maar OpenAPI is een specificatie voor het duidelijk definiëren en documenteren van REST API's. Door de eindpunten, verzoeken, antwoordformaten en dergelijke van een API te definiëren in YAML- of JSON-formaat, wordt niet alleen de communicatie met ontwikkelaars vergemakkelijkt, maar ook de generatie van front-end- en back-endcode geautomatiseerd, waardoor onnodige herhaling wordt verminderd en kleine menselijke fouten worden teruggedrongen.
Om OpenAPI op natuurlijke wijze in een project te integreren, hanteren bibliotheken in het Go-ecosysteem grofweg de volgende drie strategieën.
1. Go-commentaar samenstellen tot een OpenAPI-specificatiedocument
Een lastig aspect bij het ontwikkelen van een API conform OpenAPI is dat de daadwerkelijke documentatie en de code die deze implementeert in afzonderlijke bestanden op volledig verschillende locaties staan. Het komt daardoor vaak voor dat de code wordt bijgewerkt maar de documentatie niet, of dat de documentatie wordt bijgewerkt maar de code niet.
Een eenvoudig voorbeeld:
- Je wijzigt de logica van de API in het bestand
./internal/server/user.go - De daadwerkelijke documentatie staat in
./openapi3.yaml, en het is gemakkelijk om een wijziging daarin per ongeluk te vergeten. - Als je zonder dit probleem op te merken een Pull Request indient en collega's om een review vraagt,
- dan kunnen ook de reviewers de wijziging in
./openapi3.yamlniet zien, waardoor het ongewenste gevolg kan optreden dat de API-specificatie ongewijzigd blijft terwijl de daadwerkelijke API-implementatie wel is veranderd.
Door API-documentatie in de vorm van Go-commentaar te schrijven, kan dit probleem tot op zekere hoogte worden opgelost. Omdat code en documentatie op één plaats staan, kun je het commentaar tegelijk met de code bijwerken. Er bestaan tools die op basis van deze commentaren automatisch een OpenAPI-specificatiedocument genereren.
Een representatief project is Swag. Swag parseert het commentaar in Go-code en genereert een document in OpenAPI 2-formaat. Het gebruik is eenvoudig: je schrijft het commentaar boven de handlerfunctie in het formaat dat door de bibliotheek is vastgelegd.
1// @Summary 유저 생성
2// @Description 새로운 유저를 생성합니다.
3// @Tags Users
4// @Accept json
5// @Produce json
6// @Param user body models.User true "유저 정보"
7// @Success 200 {object} models.User
8// @Failure 400 {object} models.ErrorResponse
9// @Router /users [post]
10func CreateUser(c *gin.Context) {
11 // ...
12}
Wanneer je het commentaar op deze manier schrijft, parseert de Swag-CLI deze commentaren en genereert hij een OpenAPI 2-document. Doorgaans wordt deze taak uitgevoerd in het CI-proces, en het gegenereerde OpenAPI-specificatiedocument wordt vervolgens gepubliceerd in de Git-repository, in het uiteindelijke buildresultaat of in een apart extern systeem voor API-documentatiebeheer, zodat het bij samenwerking met andere projecten kan worden gebruikt.
Voordelen:
- Doordat het commentaar bij de code staat, is de kans kleiner dat de daadwerkelijke code en de documentatie uiteenlopen.
- Zonder extra tools of ingewikkelde configuratie kun je eenvoudig en vrijelijk documentatie opstellen louter met commentaar.
- Omdat commentaar de daadwerkelijke API-logica niet beïnvloedt, is het geschikt om tijdelijke functionaliteit toe te voegen die je niet graag als documentatie openbaar maakt.
Nadelen:
- Naarmate het aantal commentaarregels toeneemt, kan de leesbaarheid van een enkel codebestand afnemen.
- Het kan lastig zijn om alle API-specificaties uitsluitend in de vorm van commentaar weer te geven.
- Documentatie dwingt de code niet af, dus er kan geen garantie worden gegeven dat het OpenAPI-document en de daadwerkelijke logica overeenkomen.
2. Go-code genereren op basis van een OpenAPI-specificatiedocument
Er bestaat ook een methode waarbij de Single Source of Truth (SSOT) niet in de Go-code, maar in het documentbestand wordt geplaatst. Hierbij definieert men eerst de OpenAPI-specificatie en genereert men op basis daarvan Go-code. Omdat de API-specificatie de code genereert, kan het ontwerp van de API cultureel worden afgedwongen als eerste stap, en doordat het definiëren van de specificatie het eerste is wat in de ontwikkelvolgorde plaatsvindt, kan men er vroegtijdig voor zorgen dat het niet voorkomt dat na voltooiing van de ontwikkeling een gemiste aspect wordt ontdekt en de volledige code samen met een wijziging in de API-specificatie moet worden aangepast.
Representatieve projecten die deze aanpak hanteren zijn oapi-codegen en OpenAPI Generator. Het gebruik is eenvoudig.
- Schrijf een YAML- of JSON-document conform de OpenAPI-specificatie
- Voer de CLI uit
- Er wordt dan overeenkomstige Go-stubcode gegenereerd.
- Vervolgens hoef je alleen nog de logica per individuele API zelf te implementeren zodat deze stub kan worden gebruikt.
Hieronder volgt een voorbeeld van code die door oapi-codegen wordt gegenereerd.
1// StrictServerInterface represents all server handlers.
2type StrictServerInterface interface {
3 // ...
4 // Returns all pets
5 // (GET /pets)
6 FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
7 // ...
8}
De code die oapi-codegen op basis van deze interface genereert, voert taken uit zoals het parseren en valideren van query-parameters, headers en body, en roept vervolgens de juiste methode aan die in de interface is gedeclareerd. De gebruiker hoeft alleen een implementatie van deze interface te schrijven om het werk voor de API-implementatie te voltooien.
Voordelen:
- Omdat de specificatie eerst tot stand komt en daarna de ontwikkeling plaatsvindt, is het voordelig wanneer meerdere teams samenwerken om werkzaamheden parallel uit te voeren.
- Code voor werk dat voorheen repetitief en handmatig was, wordt automatisch gegenereerd, waardoor de werkefficiëntie stijgt en tegelijkertijd het debuggen nog steeds gemakkelijk blijft.
- Het is eenvoudig te garanderen dat documentatie en code altijd overeenkomen.
Nadelen:
- Voor wie weinig kennis heeft van de OpenAPI-specificatie zelf, bestaat er een zekere initiële leercurve.
- Omdat de code die de API afhandelt automatisch door het project wordt gegenereerd, kan het lastig zijn om in te spelen op situaties waarin maatwerk nodig is.
Opmerking van de auteur. Per oktober 2024 dwingt de door OpenAPI Generator gegenereerde Go-code niet alleen de API-logica maar ook de gehele projectstructuur af, en genereert zij code die vanwege de rigide structuur ongeschikt is om de diverse functionaliteiten die voor een productieomgeving nodig zijn toe te voegen. Wie voor deze aanpak kiest, wordt ten zeerste aangeraden oapi-codegen te gebruiken. De auteur gebruikt oapi-codegen + echo + StrictServerInterface.
3. OpenAPI-specificatiedocumenten genereren op basis van Go-code
Wanneer tientallen of honderden mensen aan dezelfde server ontwikkelen, ontstaat onvermijdelijk het probleem dat de consistentie per API kan verdwijnen. Een treffend voorbeeld: als de specificaties van meer dan 100 API-eindpunten in één OpenAPI YAML-bestand worden gedeclareerd, groeit dat bestand tot een monster van meer dan 10.000 regels. Bij het toevoegen van nieuwe API-eindpunten worden dan onvermijdelijk dezelfde modellen dubbel gedeclareerd, raken sommige velden verloren, of ontstaan paden die niet aan de conventies voldoen; zo begint de algehele consistentie van de API te verbrokkelen.
Om dit probleem op te lossen, kan men een aparte eigenaar voor het beheer van de OpenAPI YAML aanwijzen, of een Linter ontwikkelen die tijdens het CI-proces automatisch fouten opspoort. Men kan echter ook een Domain-specific language (DSL) in Go definiëren, zodat alle API's consistent worden gehouden.
Een representatief project dat deze techniek toepast is Kubernetes (dat zonder aparte bibliotheek intern wordt opgebouwd). Ook kun je projecten als go-restful en goa gebruiken. Hieronder volgt een voorbeeld van goa.
1var _ = Service("user", func() {
2 Method("create", func() {
3 Payload(UserPayload)
4 Result(User)
5 HTTP(func() {
6 POST("/users")
7 Response(StatusOK)
8 })
9 })
10})
Door op deze manier compileerbare Go-code te schrijven, krijg je het voordeel dat de implementatie en de documentatie van de POST /users API tegelijk worden voltooid.
Voordelen:
- Omdat alles uit de code voortkomt, is het eenvoudig om de API-consistentie over het gehele project te behouden.
- Door het sterk getypeerde systeem van Go te benutten, levert dit een nauwkeurigere en minder discutabele specificatie op dan wanneer alle functies van OpenAPI 3 worden gebruikt.
Nadelen:
- Je moet de DSL van elk framework leren, en het is niet altijd eenvoudig toe te passen op bestaande code.
- Omdat je de door het framework voorgestelde regels moet volgen, kan de vrijheid en flexibiliteit afnemen.
Afsluitende opmerkingen
Elke methode heeft voor- en nadelen, en het is belangrijk de geschikte aanpak te kiezen op basis van de eisen van het project en de voorkeur van het team. Het allerbelangrijkste is niet welke methode het beste is, maar welke oplossing het meest geschikt is voor de situatie waarin je je bevindt: maak een waardeoordeel en verhoog de ontwikkelproductiviteit, zodat je vroeg naar huis kunt en van een prettige work-life balance kunt genieten.
Hoewel dit artikel per oktober 2024 is geschreven, blijven het Go- en OpenAPI-ecosysteem zich voortdurend ontwikkelen. Houd daarom, gezien de tijd die tussen het lezen van dit artikel en het moment van schrijven verstrijkt, ook de recente ontwikkelingen van elke bibliotheek en elk project en hun gewijzigde voor- en nadelen in de gaten.
Veel plezier met Go 😘