Go och OpenAPI-ekosystemet
Introduktion
När man utvecklar en produktionsbackendserver med Go, stöter de flesta utvecklare tidigt på ett av de största problemen:
Hur dokumenterar man API:er...?
Efter en kort efterforskning inser man att det är fördelaktigt att skriva dokumentation som överensstämmer med OpenAPI-specifikationen, och man söker naturligt efter bibliotek som integreras med OpenAPI. Men även efter detta beslut uppstår nästa problem:
Det finns många bibliotek relaterade till OpenAPI... Vilket ska man använda...?
Detta dokument är en kort introduktion till bibliotek, skriven för Go-nybörjare som upplever denna situation. Dokumentet är skrivet i slutet av 2024, och eftersom språkmiljön alltid är i förändring, rekommenderas det att du refererar till det samtidigt som du håller dig uppdaterad om den senaste utvecklingen.
Bibliotekens strategier för att hantera OpenAPI
Som du kanske redan vet, är OpenAPI en specifikation för att tydligt definiera och dokumentera REST API:er. Genom att definiera API:ernas endpoints, förfrågningar och svarsformat i YAML- eller JSON-format, hjälper det inte bara utvecklare utan även frontend- och backend-kodgenerering, vilket minskar meningslösa upprepningar och små mänskliga fel.
För att integrera OpenAPI naturligt i projektet, antar Go-ekosystemets bibliotek i stort sett följande tre strategier:
1. Kombinera Go-kommentarer till OpenAPI-specifikationsdokument
En av de svåra sakerna när man utvecklar API:er i enlighet med OpenAPI är att den faktiska dokumentationen och koden som implementerar den finns i separata filer på helt olika platser. Detta leder till situationer där man uppdaterar koden men inte dokumentationen, eller uppdaterar dokumentationen men inte koden.
Ett enkelt exempel:
- Du ändrar logiken för ett API i filen
./internal/server/user.go
. - Den faktiska dokumentationen finns i
./openapi3.yaml
, och du kan av misstag glömma att göra ändringar där. - Om du inte är medveten om detta problem och skickar in en Pull Request för granskning av kollegor,
- Eftersom granskarna inte ser några ändringar i
./openapi3.yaml
, kan det inträffa en olycklig situation där API-specifikationen är densamma men den faktiska API-implementeringen har ändrats.
Genom att skriva API-dokumentationen i form av Go-kommentarer kan detta problem delvis lösas. Eftersom koden och dokumentationen finns på samma ställe kan du uppdatera kommentarerna samtidigt som du ändrar koden. Det finns verktyg som automatiskt genererar OpenAPI-specifikationsdokument baserat på dessa kommentarer.
Ett representativt projekt är Swag. Swag parsar kommentarer i Go-kod och genererar dokument i OpenAPI 2-format. Användningen är enkel. Skriv bara kommentarer enligt det format som definierats av varje bibliotek ovanför hanteringsfunktionen.
1// @Summary Skapa användare
2// @Description Skapar en ny användare.
3// @Tags Users
4// @Accept json
5// @Produce json
6// @Param user body models.User true "Användarinformation"
7// @Success 200 {object} models.User
8// @Failure 400 {object} models.ErrorResponse
9// @Router /users [post]
10func CreateUser(c *gin.Context) {
11 // ...
12}
När dessa kommentarer är skrivna, kommer CLI-verktyget Swag att parsa dem och generera ett OpenAPI 2-dokument. Vanligtvis görs detta som en del av CI-processen, och dokumentet med OpenAPI-specifikationen som genereras distribueras till Git-förrådet, den slutliga byggprodukten eller ett separat externt API-dokumenthanteringssystem för användning i samarbete med andra projekt.
Fördelar:
- Eftersom kommentarerna finns tillsammans med koden, minskar risken att den faktiska koden och dokumentationen skiljer sig åt.
- Du kan enkelt och fritt dokumentera med bara kommentarer, utan separata verktyg eller komplicerade inställningar.
- Eftersom kommentarer inte påverkar den faktiska API-logiken är det bra att lägga till temporära funktioner som är svåra att publicera i dokumentationen.
Nackdelar:
- Med ett stort antal kommentarer kan läsbarheten för enskilda kodfiler försämras.
- Det kan vara svårt att uttrycka alla API-specifikationer i form av kommentarer.
- Eftersom dokumentet inte tvingar koden kan man inte garantera att OpenAPI-dokumentationen och den faktiska logiken stämmer överens.
2. Generera Go-kod från OpenAPI-specifikationsdokument
Det finns också ett sätt att placera Single source of Truth (SSOT) på dokumentsidan, inte Go-koden. Det är metoden att först definiera OpenAPI-specifikationen och sedan generera Go-kod baserat på det definierade innehållet. Eftersom API-specifikationen genererar koden kan utvecklingskulturen tvinga fram API-design först, och eftersom definitionen av API-specifikationen är det första steget i utvecklingsordningen, kan man tidigt förhindra olyckliga situationer där man inser missade delar först efter att utvecklingen är klar och hela koden måste ändras tillsammans med API-specifikationsändringarna.
Representativa projekt som antar denna metod är oapi-codegen och OpenAPI Generator. Användningen är enkel:
- Skriv ett YAML- eller JSON-dokument i enlighet med OpenAPI-specifikationen.
- Kör CLI-verktyget.
- Motsvarande Go stub-kod genereras.
- Implementera sedan detaljlogiken för varje API så att denna stub kan användas.
Följande är ett exempel på kod som genereras av oapi-codegen:
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}
Med detta interface som medium utför koden som genereras av oapi-codegen logik som query parameters, header, body parsing och validering, och anropar den lämpliga metoden som deklarerats i interfacet. Användaren behöver bara implementera en implementation av ovanstående interface för att slutföra arbetet som krävs för API-implementeringen.
Fördelar:
- Eftersom specifikationen kommer först och utvecklingen fortsätter, är det lättare att utföra arbete parallellt om flera team samarbetar.
- Eftersom koden för repetitivt arbete genereras automatiskt, ökar effektiviteten samtidigt som felsökningen fortfarande är enkel.
- Det är lätt att garantera att dokumentet och koden alltid matchar.
Nackdelar:
- Om du är ovetande om själva OpenAPI-specifikationen finns det en viss inlärningskurva i början.
- Eftersom formen på koden som hanterar API:et genereras automatiskt av projektet kan det vara svårt att anpassa om anpassning krävs.
Författarens kommentar. Från och med oktober 2024 tvingar Go-koden som genereras av OpenAPI Generator projektets hela form, och projektets struktur är styv, vilket skapar kod som är olämplig för att lägga till olika funktioner som krävs i en verklig produktionsmiljö. De som väljer denna metod rekommenderas starkt att använda oapi-codegen. Jag använder oapi-codege + echo + StrictServerInterface.
3. Generera OpenAPI-specifikationsdokument med Go-kod
En oundviklig fråga som uppstår när dussintals eller hundratals personer utvecklar samma server är att enskilda API:er kan sakna enhetlighet. Ett intuitivt exempel är att om specifikationerna för mer än 100 API-endpoints deklareras i en enda OpenAPI YAML-fil, kommer den filen att vara ett monster på över 10 000 rader. När du deklarerar en ny API-endpoint kommer du oundvikligen att deklarera samma modell i flera instanser eller utelämna vissa fält, eller så kan det uppstå ett Path-namn som inte följer konventionen, vilket leder till att den övergripande API-enhetligheten börjar brytas ned.
För att lösa dessa problem kan man tillsätta en separat ägare som hanterar OpenAPI YAML eller utveckla en Linter som automatiskt kan identifiera dem under CI-processen. Man kan också definiera ett domänspecifikt språk (DSL) i Go för att tvinga alla API:er att ha konsekvent enhetlighet.
Ett representativt projekt som använder denna teknik är Kubernetes (byggt internt utan ett separat bibliotek). Du kan också använda projekt som go-restful, goa. Följande är ett exempel på användning av 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})
Genom att skriva kompilerbar Go-kod som ovan kan man uppnå fördelen att implementeringen av API:et POST /users
och definitionen av dokumentationen slutförs samtidigt.
Fördelar:
- Eftersom allt kommer från koden är det lätt att upprätthålla API-konsistens i hela projektet.
- Genom att använda Go:s starka typsystem kan man få en mer exakt och obestridlig specifikation än när man använder alla funktioner i OpenAPI3.
Nackdelar:
- Du måste lära dig den DSL som definieras av varje ramverk, och det kan vara svårt att tillämpa det på befintlig kod.
- Eftersom du måste följa reglerna som föreslås av ramverket kan friheten och flexibiliteten minska.
Avslutningsvis
Varje metod har sina fördelar och nackdelar, och det är viktigt att välja den metod som passar bäst för projektets behov och teamets preferenser. Det viktigaste är inte vilken metod som är bäst, utan att utvärdera vilket som är den bästa lösningen för den aktuella situationen och öka produktiviteten för att kunna gå hem tidigt och njuta av en tillfredsställande balans mellan arbete och fritid.
Även om detta är skrivet i oktober 2024, utvecklas Go- och OpenAPI-ekosystemet kontinuerligt. Tänk därför på tidsintervallet när du läser detta och följ upp de senaste nyheterna om varje bibliotek och projekt, samt deras ändrade fördelar och nackdelar.
Ha ett lyckligt Go-liv! 😘