Go og OpenAPI økosystemet
Introduktion
Når man udvikler en Production Backend-server i Go, er en af de første udfordringer, som de fleste udviklere støder på, følgende:
Hvordan dokumenterer jeg API'er...?
Efter en kort søgning indser man fordelene ved at udarbejde dokumentation i overensstemmelse med OpenAPI-specifikationen, og man finder naturligt et bibliotek, der er integreret med OpenAPI. Men selv efter denne beslutning opstår det næste problem:
Der er mange biblioteker relateret til OpenAPI... Hvilket skal jeg bruge...?
Denne artikel er skrevet som en kort introduktion til biblioteker for Go-begyndere, der oplever denne situation. Den er skrevet i slutningen af 2024, og da sprogøkosystemet altid er i konstant udvikling, anbefales det at holde sig opdateret med de seneste tendenser, mens man bruger den som reference.
Bibliotekers strategier til at håndtere OpenAPI
Som du måske allerede ved, er OpenAPI en specifikation til tydeligt at definere og dokumentere REST API'er. Ved at definere API'ens endepunkter, anmodninger og svarformater i YAML- eller JSON-format, kan man automatisere genereringen af kode for både udviklere, frontend og backend, hvilket reducerer meningsløse gentagelser og hjælper med at mindske mindre menneskelige fejl.
For at integrere OpenAPI naturligt i et projekt anvender bibliotekerne i Go-økosystemet primært følgende tre strategier:
1. Kombinér Go-kommentarer til et OpenAPI-specifikationsdokument
En af de vanskelige ting ved API-udvikling i overensstemmelse med OpenAPI er, at den faktiske dokumentation og den kode, der implementerer dokumentationen, eksisterer som separate filer på helt forskellige steder. Derfor sker det overraskende ofte, at koden opdateres, men dokumentationen ikke opdateres, eller dokumentationen opdateres, men koden ikke opdateres.
Et simpelt eksempel:
- Du har ændret logikken for en API i filen
./internal/server/user.go
. - Den faktiske dokumentation findes i
./openapi3.yaml
, og du glemmer ved en fejl at opdatere den. - Hvis du ikke er opmærksom på dette problem og sender en Pull Request og får den gennemgået af kolleger.
- Gennemlæserne kan heller ikke se ændringerne i
./openapi3.yaml
. Derfor kan der opstå den uheldige situation, at API-specifikationen er den samme, men den faktiske API-implementering er blevet ændret.
Ved at skrive API-dokumentation i form af Go-kommentarer kan dette problem løses til en vis grad. Da kode og dokumentation er samlet på ét sted, kan du opdatere kommentarerne sammen med koden. Der findes værktøjer, der automatisk genererer OpenAPI-specifikationsdokumenter baseret på disse kommentarer.
Et fremtrædende projekt er Swag. Swag parser Go-kodens kommentarer og genererer dokumentation i OpenAPI 2-format. Brugen er enkel. Du skal blot skrive kommentarer i det format, der er defineret af hvert bibliotek, over handler-funktionen.
1// @Summary Opret bruger
2// @Description Opretter en ny bruger.
3// @Tags Users
4// @Accept json
5// @Produce json
6// @Param user body models.User true "Brugerinformation"
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 du skriver kommentarer som ovenstående, parser Swag CLI disse kommentarer og genererer et OpenAPI 2-dokument. Generelt udføres denne handling i CI-processen, og det genererede OpenAPI-specifikationsdokument distribueres til Git Repository, det endelige build-output og et separat eksternt API-dokumentadministrationssystem, hvor det bruges til samarbejde med andre projekter.
Fordele:
- Da kommentarer er placeret sammen med koden, er der mindre sandsynlighed for, at den faktiske kode og dokumentation afviger.
- Du kan nemt og frit dokumentere med blot kommentarer uden separate værktøjer eller komplekse indstillinger.
- Da kommentarer ikke påvirker den faktiske API-logik, er det godt til at tilføje midlertidige funktioner, der er for følsomme til at blive offentliggjort som dokumentation.
Ulemper:
- Læseligheden af en enkelt kodefil kan falde, da antallet af kommentarlinjer stiger.
- Det kan være vanskeligt at udtrykke alle API-specifikationer i kommentarform.
- Da dokumentationen ikke tvinger koden, er der ingen garanti for, at OpenAPI-dokumentationen stemmer overens med den faktiske logik.
2. Generér Go-kode fra et OpenAPI-specifikationsdokument
Der findes også en metode til at placere Single source of Truth (SSOT) på dokumentationssiden i stedet for Go-koden. Det er metoden til først at definere OpenAPI-specifikationen og derefter generere Go-kode baseret på det definerede indhold. Da API-specifikationen snart vil generere kode, kan du tvinge API-design først i udviklingskulturen, og da API-specifikationen er det første, der starter i udviklingsrækkefølgen, er det muligt at forhindre uheldige hændelser tidligt, hvor du først indser de manglende dele, efter at udviklingen er afsluttet, og hele koden ændres sammen med API-specifikationsændringen.
Repræsentative projekter, der anvender denne metode, omfatter oapi-codegen og OpenAPI Generator. Brugen er enkel.
- Skriv et yaml- eller json-dokument i overensstemmelse med OpenAPI-specifikationen.
- Kør CLI.
- Den tilsvarende Go stub-kode genereres.
- Nu skal du kun implementere den detaljerede logik for hver API, så denne stub kan bruges.
Følgende er et eksempel på kode, der genereres af oapi-codegen.
1// StrictServerInterface represents all server handlers.
2// StrictServerInterface repræsenterer alle server-handlers.
3type StrictServerInterface interface {
4 // ...
5 // Returns all pets
6 // Returnerer alle kæledyr
7 // (GET /pets)
8 FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
9 // ...
10}
Gennem ovenstående interface udfører den kode, der er genereret af oapi-codegen, logik som query parameters, header, body parsing og Validering, og kalder den relevante metode, der er erklæret i interfacet. Brugeren behøver kun at implementere implementeringen af ovenstående interface for at fuldføre det arbejde, der kræves til API-implementering.
Fordele:
- Da specifikationen kommer først, og udviklingen skrider frem, er det nemt at udføre arbejde parallelt, når flere teams samarbejder.
- Koden for den del, der blev arbejdet med gentagne kedelige opgaver, genereres automatisk, så arbejdseffektiviteten stiger, og fejlfinding er stadig let.
- Det er let at sikre, at dokumentationen og koden altid stemmer overens.
Ulemper:
- Hvis du er uvidende om selve OpenAPI-specifikationen, vil der være en vis indledende læringskurve.
- Da formen på koden, der håndterer API'en, genereres automatisk af projektet, kan det være vanskeligt at reagere, hvis der er behov for tilpasning.
Forfatterens kommentar. I oktober 2024 tvinger Go-koden, der er genereret af OpenAPI Generator, ikke kun API-logikken, men også hele projektets form, og projektets struktur er stiv, så den genererer en kodeform, der er uegnet til at tilføje forskellige funktioner, der er nødvendige i et faktisk produktionsmiljø. Hvis du vælger denne metode, anbefales det kraftigt, at du bruger oapi-codegen. Forfatteren bruger oapi-codege + echo + StrictServerInterface.
3. Generér OpenAPI-specifikationsdokumenter med Go-kode
En problemstilling, der uundgåeligt opstår, når snesevis eller hundredvis af mennesker udvikler den samme server, er, at ensartetheden kan brydes for hver enkelt API. Som et intuitivt eksempel, hvis du erklærer specifikationerne for mere end 100 API-endepunkter i en enkelt OpenAPI yaml-fil, vil filen være et monster på over 10.000 linjer, og når du erklærer et nyt API-endepunkt, vil du uundgåeligt duplikere den samme model eller udelade et par felter, eller der vil opstå en Path-navngivning, der ikke er i overensstemmelse med konventionen, og den overordnede API-ensartethed begynder at brydes.
For at løse disse problemer kan du have en separat Owner til at administrere OpenAPI yaml, eller du kan udvikle en Linter og træffe foranstaltninger for automatisk at fange den under CI-processen, men du kan definere et Domain-specific language (DSL) i Go-sproget for at tvinge alle API'er til at have ensartet ensartethed.
Et repræsentativt projekt, der bruger denne teknik, er Kubernetes (selvbygget uden et separat bibliotek), og du kan bruge det ved at bruge projekter som go-restful og goa. Følgende er et eksempel på brug af 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})
Ved at skrive kompilerbar Go-kode som ovenstående kan du samtidig opnå fordelene ved at fuldføre implementeringen og dokumentdefinitionen for POST /users
API'en.
Fordele:
- Da alt kommer fra koden, er det nemt at bevare API-konsistens i hele projektet.
- Ved at udnytte Go's stærke typesystem kan du opnå en mere præcis og uomtvistelig specifikation end når du bruger alle funktionerne i OpenAPI3.
Ulemper:
- Du skal lære DSL'en, der er defineret i hver framework, og det kan være vanskeligt at anvende den på eksisterende kode.
- Da du er tvunget til at følge de regler, der foreslås af frameworket, kan friheden og fleksibiliteten reduceres.
Afslutningsvis
Hver metode har sine fordele og ulemper, og det er vigtigt at vælge den metode, der passer til projektets krav og teamets præferencer. Det vigtigste er altid at vurdere, hvilken løsning der passer bedst til din nuværende situation, og at opretholde en høj udviklingsproduktivitet for at gå tidligt hjem og nyde en tilfredsstillende balance mellem arbejde og privatliv.
Selvom jeg skriver denne artikel i oktober 2024, udvikler Go og OpenAPI-økosystemet sig konstant, så husk at følge med i de seneste tendenser for hvert bibliotek og projekt og deres ændrede fordele og ulemper, når du læser denne artikel.
Hav et godt Go-liv~ 😘