Go en het OpenAPI-ecosysteem
Inleiding
Wanneer men een Production Backend server in Go ontwikkelt, is een van de eerste uitdagingen waar bijna alle ontwikkelaars mee te maken krijgen, de volgende:
Hoe documenteer ik de API...?
Na enig onderzoek komt men erachter dat het nuttig is om documentatie te schrijven die voldoet aan de OpenAPI-specificatie, en gaat men vanzelfsprekend op zoek naar een bibliotheek die met OpenAPI samenwerkt. Maar zelfs met deze beslissing, blijft er nog een probleem:
Er zijn zoveel OpenAPI-gerelateerde bibliotheken... welke moet ik gebruiken...?
Dit document is een korte introductie van bibliotheken, geschreven voor Go-beginners die deze situatie ervaren. Het document is geschreven in eind 2024. Aangezien het ecosysteem van de taal altijd dynamisch verandert, wordt aanbevolen om, met dit in het achterhoofd, altijd de laatste ontwikkelingen te volgen.
Strategieën van bibliotheken voor OpenAPI
Zoals u waarschijnlijk al weet, is OpenAPI een specificatie om REST API's duidelijk te definiëren en documenteren. Door de eindpunten, aanvragen en antwoordformaten van een API in YAML- of JSON-formaat te definiëren, wordt niet alleen ontwikkelaars geholpen, maar worden ook de frontend- en backend-code generatie geautomatiseerd, wat onnodige herhaling vermindert en menselijke fouten minimaliseert.
Om OpenAPI op een natuurlijke manier in het project te integreren, volgen de bibliotheken in het Go-ecosysteem over het algemeen drie belangrijke strategieën.
1. Go-commentaren combineren tot een OpenAPI-specificatiedocument
Een van de lastige aspecten bij het ontwikkelen van API's volgens de OpenAPI-specificatie is dat de daadwerkelijke documentatie en de code die deze documentatie implementeert, zich in afzonderlijke bestanden op totaal verschillende locaties bevinden. Dit betekent dat er vaak situaties voorkomen waarin de code is bijgewerkt, maar de documentatie niet, of andersom.
Een eenvoudig voorbeeld:
- Stel dat de logica voor een API in een bestand genaamd
./internal/server/user.go
is gewijzigd. - De daadwerkelijke documentatie bevindt zich in
./openapi3.yaml
en de bijbehorende wijziging wordt per ongeluk vergeten. - Als een pull-request wordt ingediend zonder deze wijzigingen op te merken, en de wijzigingen worden beoordeeld door collega's,
- de reviewers zien de wijzigingen in
./openapi3.yaml
mogelijk ook niet, wat kan leiden tot de ongelukkige situatie dat de API-specificatie ongewijzigd blijft, terwijl de daadwerkelijke API-implementatie wel is gewijzigd.
Het schrijven van API-documentatie in de vorm van Go-commentaren kan dit probleem gedeeltelijk oplossen. Omdat de code en de documentatie op één plaats zijn samengebracht, kan men de commentaren bijwerken tegelijk met het bewerken van de code. Er zijn hulpmiddelen die automatisch OpenAPI-specificatiedocumenten genereren op basis van deze commentaren.
Een representatief project is Swag. Swag parseert commentaren in Go-code en genereert documenten in OpenAPI 2-formaat. Het gebruik is eenvoudig. Men schrijft commentaren in het formaat dat door elke bibliotheek is gedefinieerd, boven de handler-functie.
1// @Summary Gebruiker aanmaken
2// @Description Maakt een nieuwe gebruiker aan.
3// @Tags Gebruikers
4// @Accept json
5// @Produce json
6// @Param user body models.User true "Gebruikersinformatie"
7// @Success 200 {object} models.User
8// @Failure 400 {object} models.ErrorResponse
9// @Router /users [post]
10func CreateUser(c *gin.Context) {
11 // ...
12}
Wanneer zulke commentaren zijn geschreven, parseert de CLI van Swag deze commentaren om een OpenAPI 2-document te genereren. Over het algemeen wordt deze taak uitgevoerd in het CI-proces, en het gegenereerde OpenAPI-specificatiedocument wordt gedistribueerd naar een Git Repository, de uiteindelijke build, of een extern API-documentbeheersysteem, waar het wordt gebruikt voor samenwerking met andere projecten.
Voordelen:
- Omdat de commentaren bij de code staan, is de kans kleiner dat de daadwerkelijke code en de documentatie van elkaar afwijken.
- Men kan eenvoudig en flexibel documentatie maken met behulp van commentaren, zonder aparte tools of ingewikkelde instellingen.
- Omdat commentaren geen invloed hebben op de daadwerkelijke API-logica, is het handig om tijdelijke functies toe te voegen die men niet graag wil publiceren in de documentatie.
Nadelen:
- Wanneer het aantal regels commentaren toeneemt, kan de leesbaarheid van een enkel codebestand afnemen.
- Het kan moeilijk zijn om alle API-specificaties in de vorm van commentaren uit te drukken.
- Omdat de documentatie niet de code afdwingt, is er geen garantie dat de OpenAPI-documentatie en de daadwerkelijke logica overeenkomen.
2. Go-code genereren uit een OpenAPI-specificatiedocument
Er is ook een manier om de Single Source of Truth (SSOT) niet in de Go-code, maar in het document te plaatsen. Dit is de methode waarbij eerst de OpenAPI-specificatie wordt gedefinieerd, en op basis daarvan Go-code wordt gegenereerd. Omdat de API-specificatie code genereert, kan men in de ontwikkelcultuur het ontwerpen van de API eerst afdwingen. Ook, omdat het definiëren van de API-specificatie de eerste stap is in het ontwikkelproces, kan voorkomen worden dat men pas na voltooiing van de ontwikkeling delen mist, wat leidt tot het wijzigen van de API-specificatie en de volledige code.
Representatieve projecten die deze methode gebruiken, zijn oapi-codegen en OpenAPI Generator. Het gebruik is eenvoudig.
- Men schrijft een YAML- of JSON-document dat voldoet aan de OpenAPI-specificatie.
- Wanneer de CLI wordt uitgevoerd,
- wordt de corresponderende Go stub-code gegenereerd.
- Nu hoeft men alleen nog de gedetailleerde logica voor elke API te implementeren, zodat deze stub kan worden gebruikt.
Hieronder staat 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}
Met deze interface als tussenstuk voert de code die door oapi-codegen wordt gegenereerd, logica uit zoals het parseren en valideren van query parameters, headers en body's, en roept het de geschikte methode aan die in de interface is gedefinieerd. De gebruiker hoeft alleen een implementatie van de bovenstaande interface te realiseren, en het werk dat nodig is voor de API-implementatie is voltooid.
Voordelen:
- Omdat de specificatie eerst komt en de ontwikkeling daarna pas wordt uitgevoerd, is het gemakkelijk om werk parallel uit te voeren wanneer er meerdere teams samenwerken.
- Omdat code voor repetitief werk automatisch wordt gegenereerd, neemt de efficiëntie van het werk toe terwijl het debuggen nog steeds gemakkelijk is.
- Het is makkelijk om te garanderen dat de documentatie en de code altijd hetzelfde zijn.
Nadelen:
- Als men geen kennis heeft van de OpenAPI-specificatie, kan er een leercurve zijn.
- Omdat de vorm van de code die API's afhandelt automatisch door het project wordt gegenereerd, kan het moeilijk zijn om aanpassingen te maken wanneer dit nodig is.
Commentaar van de auteur. Vanaf oktober 2024 dwingt de Go-code die door OpenAPI Generator wordt gegenereerd, de vorm van het volledige project af, en is de structuur van het project rigide, waardoor het genereren van code die niet geschikt is om diverse functies toe te voegen die nodig zijn in een echte productieomgeving. Degenen die deze methode kiezen, wordt sterk aangeraden om oapi-codegen te gebruiken. De auteur zelf gebruikt oapi-codege + echo + StrictServerInterface.
3. OpenAPI-specificatiedocumenten genereren met Go-code
Wanneer tientallen of honderden mensen aan dezelfde server werken, is een onvermijdelijk probleem dat de uniformiteit van individuele API's verloren kan gaan. Een intuïtief voorbeeld: als de specificaties voor meer dan 100 API-eindpunten worden gedeclareerd in één OpenAPI YAML-bestand, zal dit bestand een monster worden van meer dan 10.000 regels. Bij het declareren van een nieuw API-eindpunt, zullen noodzakelijkerwijs dezelfde modellen dubbel worden gedeclareerd, of zullen bepaalde velden worden weggelaten, of zullen er padnamen ontstaan die niet voldoen aan de conventies. De algemene consistentie van de API zal dus beginnen te breken.
Om dit probleem op te lossen, kan men een aparte eigenaar voor het beheer van de OpenAPI YAML aanstellen, of een Linter ontwikkelen om dit automatisch te detecteren tijdens het CI-proces. Maar men kan ook een Domain-Specific Language (DSL) in Go definiëren, zodat alle API's een consistente uniformiteit hebben.
Een representatief project dat deze techniek gebruikt is Kubernetes (onafhankelijk ontwikkeld zonder bibliotheek), maar men kan ook projecten zoals go-restful en goa gebruiken. Hieronder staat een voorbeeld van het gebruik 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 compileerbare Go-code zoals hierboven te schrijven, kan men het voordeel behalen dat de implementatie en de documentatiedefinitie voor de POST /users
API tegelijkertijd worden voltooid.
Voordelen:
- Omdat alles uit de code komt, is het gemakkelijk om de consistentie van de API's in het hele project te behouden.
- Door gebruik te maken van het sterk getypeerde systeem van Go kan men een nauwkeurigere en onbetwistbare specificatie verkrijgen dan wanneer alle functies van OpenAPI3 worden gebruikt.
Nadelen:
- Men moet de DSL leren die door elk framework is gedefinieerd, en het kan moeilijk zijn om het toe te passen op bestaande code.
- Omdat men de regels moet volgen die door het framework worden voorgesteld, kan de vrijheid en flexibiliteit afnemen.
Tot slot
Elke methode heeft zijn voor- en nadelen, en het is belangrijk om de juiste methode te kiezen op basis van de projectvereisten en de voorkeuren van het team. Het belangrijkste is niet welke aanpak het beste is, maar om te beoordelen welke oplossing het meest geschikt is voor de huidige situatie, en om de productiviteit van de ontwikkeling te verhogen, en zo te kunnen genieten van snel naar huis gaan en een tevreden work-life balance.
Hoewel dit artikel is geschreven in oktober 2024, ontwikkelen het Go- en OpenAPI-ecosysteem zich voortdurend, dus houd rekening met het tijdsverschil wanneer u dit artikel leest en volg de laatste ontwikkelingen van elke bibliotheek en project, inclusief hun gewijzigde voor- en nadelen.
Ik wens je een gelukkig Go-leven! 😘