Go og OpenAPI-økosystemet

Innledning
Når man utvikler en Production Backend-server med Go, står nesten alle utviklere tidligt overfor et av de første vanskelige problemene, nemlig:
API-dokumentasjon, hvordan skal vi gjøre dette...?
Ved å søke litt finner man ut at det er nyttig å skrive dokumentasjon i samsvar med OpenAPI-spesifikasjonen, og man begynner naturlig nok å lete etter biblioteker som integreres med OpenAPI. Men selv etter at denne avgjørelsen er tatt, oppstår neste problem.
Det finnes mange OpenAPI-relaterte biblioteker... hvilket skal jeg bruke...?
Dette dokumentet er en kort introduksjon til biblioteker, skrevet for Go-nybegynnere som står i denne situasjonen. Det er utarbeidet per slutten av 2024, og da økosystemet for programmeringsspråk stadig endrer seg, anbefales det å bruke dette som referanse og samtidig holde seg oppdatert på de nyeste utviklingene.
Strategier for OpenAPI i bibliotekene
Dette er sannsynligvis kjent for deg allerede, men OpenAPI er en spesifikasjon for å definere og dokumentere REST API-er på en tydelig måte. Ved å definere API-ets endepunkter, forespørsler og responsformater i YAML- eller JSON-format, hjelper det betydelig både utviklere og kode for frontend og backend ved å automatisere kodegenerering, redusere meningsløs repetisjon og minske mindre menneskelige feil.
For å integrere OpenAPI naturlig i prosjektet bruker bibliotekene i Go-økosystemet i hovedsak følgende tre strategier.
1. Sammensetning av OpenAPI-spesifikasjonsdokumenter fra Go-kommentarer
En vanskelig side ved å utvikle API-er i henhold til OpenAPI er at selve dokumentasjonen og koden som implementerer den ligger i separate filer på helt ulike steder. Det er ganske ofte at koden oppdateres uten at dokumentet oppdateres, eller at dokumentet oppdateres uten at koden blir oppdatert.
Et enkelt eksempel:
- Du endrer API-logikken i filen
./internal/server/user.go - Den faktiske dokumentasjonen ligger i
./openapi3.yaml, og du kan ved en feil glemme å endre den. - Hvis du ikke oppdager dette problemet, sender du en Pull Request og får gjennomgang fra kollegaene.
- Også reviewerne ser ikke endringene i
./openapi3.yaml, så det kan skje at API-spesifikasjonen forblir den samme, mens den faktiske API-implementasjonen har endret seg.
Ved å skrive API-dokumentasjon som Go-kommentarer kan man løse dette problemet i en viss grad. Fordi kode og dokumentasjon ligger på ett sted, kan man oppdatere kommentarene samtidig som man endrer koden. Det finnes verktøy som automatisk genererer OpenAPI-spesifikasjonsdokumenter basert på disse kommentarene.
Et representativt prosjekt er Swag. Swag analyserer kommentarene i Go-koden og genererer dokumentasjon i OpenAPI 2-format. Bruken er enkel: Du skriver kommentarer over behandlerfunksjonen i formatet som er definert av biblioteket.
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}
Når kommentarene er skrevet slik, analyserer Swag-CLI-en disse kommentarene og genererer et OpenAPI 2-dokument. Vanligvis utføres dette i CI-prosessen, og det genererte OpenAPI-spesifikasjonsdokumentet publiseres i Git-repositoriet, i de endelige byggeresultatene eller i et eksternt system for administrasjon av API-dokumentasjon, slik at det kan brukes i samarbeid med andre prosjekter.
Fordeler:
- Fordi kommentarene ligger sammen med koden, er det mindre sannsynlig at den faktiske kodens form og dokumentasjonen avviker.
- Uten egne verktøy eller komplisert oppsett kan man dokumentere enkelt og fritt med kun kommentarer.
- Kommentarene påvirker ikke den faktiske API-logikken, så det er godt egnet for å legge til midlertidige funksjoner som man ikke ønsker å eksponere i dokumentasjonen.
Ulemper:
- Når antall kommentarlinjer øker, kan lesbarheten til en enkelt kodefil bli dårligere.
- Det kan være vanskelig å uttrykke alle API-spesifikasjoner med kommentarer.
- Dokumentasjonen tvinger ikke koden til å samsvare med den, så det finnes ingen garanti for at OpenAPI-dokumentasjonen og den faktiske logikken er konsistente.
2. Generering av Go-kode fra OpenAPI-spesifikasjonsdokumenter
Det finnes også en metode der Single Source of Truth (SSOT) plasseres i dokumentet i stedet for i Go-koden. Man definerer først OpenAPI-spesifikasjonen, og genererer deretter Go-kode basert på det som er definert. Fordi API-spesifikasjonen genererer koden, kan man i utviklingskulturen håndheve at API-designet gjøres først. Siden definisjonen av API-spesifikasjonen er det første som gjøres i utviklingsrekkefølgen, har denne metoden den styrken at man tidlig kan forhindre at man oppdager manglende deler først etter at utviklingen er ferdig, og at hele koden må endres sammen med endringer i API-spesifikasjonen.
Representative prosjekter som bruker denne metoden er oapi-codegen og OpenAPI Generator. Bruken er enkel.
- Skriv et YAML- eller JSON-dokument i samsvar med OpenAPI-spesifikasjonen
- Kjør CLI-en
- Tilsvarende Go-stub-kode genereres
- Nå trenger du bare å implementere den detaljerte logikken for hvert enkelt API, slik at stub-koden kan brukes
Følgende er et eksempel på kode generert 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}
Koden som oapi-codegen genererer basert på dette interfacet, utfører logikk for parsing og validering av query-parametere, headere og body, og kaller deretter den passende metoden som er deklarert i interfacet. Brukeren trenger bare å implementere en konkret type for dette interfacet, så er arbeidet som kreves for API-implementasjonen fullført.
Fordeler:
- Fordi spesifikasjonen kommer først og utviklingen skjer deretter, er det gunstig for flere team som samarbeider, å utføre arbeidet parallelt.
- Kode for det som tidligere var repetitivt manuelt arbeid, genereres automatisk, så arbeidseffektiviteten øker, samtidig som det fortsatt er gunstig for feilsøking.
- Det er enklere å garantere at dokumentasjon og kodeform alltid samsvarer.
Ulemper:
- For de som ikke kjenner OpenAPI-spesifikasjonen godt, finnes det en viss innledende læringskurve.
- Formen på koden som håndterer API-et genereres automatisk av prosjektet, så det kan være vanskelig å håndtere tilfeller som krever tilpasning.
Forfatterens kommentar. Per oktober 2024 genererer OpenAPI Generator Go-kode som ikke bare begrenser API-logikken, men hele prosjektstrukturen, og den genererte koden har en stiv struktur som gjør den lite egnet for å legge til de ulike funksjonene som trengs i et faktisk produksjonsmiljø. For dem som velger denne metoden, anbefales det sterkt å bruke oapi-codegen. Forfatteren bruker oapi-codegen + echo + StrictServerInterface.
3. Generering av OpenAPI-spesifikasjonsdokumenter fra Go-kode
Når dziesiątki, hundrevis av mennesker utvikler på samme server, oppstår nødvendigvis det problemet at konsistensen mellom de enkelte API-ene kan brytes. Et tydelig eksempel: Hvis spesifikasjonen for over 100 API-endepunkter deklareres i en enkelt OpenAPI YAML-fil, vil filen bli et monster på over 10 000 linjer. Når nye API-endepunkter legges til, vil samme modell nødvendigvis bli deklarert flere ganger, noen felt vil bli utelatt, eller det vil oppstå stinamn som ikke følger konvensjonene. Dermed begynner den helhetlige konsistensen i API-et å bryte sammen.
For å løse dette kan man utpeke en eier som administrerer OpenAPI YAML-filen, eller utvikle en linter og sørge for at den fanger opp problemer automatisk under CI-prosessen. Man kan også bruke Go til å definere et Domain-specific language (DSL), slik at alle API-er tvinges til å ha en enhetlig konsistens.
Et representativt prosjekt som bruker denne teknikken er Kubernetes, som bygger dette selv uten eksterne biblioteker. Man kan også prøve prosjekter som go-restful og goa. Følgende er et eksempel på bruk 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})
Når man skriver kompilerbar Go-kode slik som dette, får man den styrken at implementasjonen av POST /users-API-et og definisjonen av dokumentasjonen fullføres samtidig.
Fordeler:
- Fordi alt genereres fra koden, er det enkelt å opprettholde API-konsistens for hele prosjektet.
- Ved å utnytte Gos sterke typesystem kan man få en mer presis og mindre omstridt spesifikasjon enn ved å bruke alle funksjonene i OpenAPI3.
Ulemper:
- Man må lære DSL-en som hvert rammeverk har definert, og det kan være vanskelig å anvende på eksisterende kode.
- Fordi man må følge reglene som rammeverket foreslår, kan frihet og fleksibilitet bli redusert.
Avslutning
Hver metode har sine fordeler og ulemper, og det er viktig å velge den metoden som passer best til prosjektets krav og teamets preferanser. Det viktigste er ikke hvilken metode som er best, men å vurdere hvilken løsning som passer best for din egen situasjon, og å øke utviklingsproduktiviteten slik at du kan få tidlig fyraften og god balanse mellom arbeid og fritid.
Selv om artikkelen er skrevet med utgangspunkt i oktober 2024, utvikler økosystemene for Go og OpenAPI seg kontinuerlig. Ta hensyn til tiden som har gått siden du leser dette, og følg fortsatt med på nyheter og endrede for- og ulemper ved de enkelte bibliotekene og prosjektene.
Lykke til med Go-livet 😘