GoSuda

Go og OpenAPI-økosystemet

By iwanhae views ...

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:

  1. Du endrer API-logikken i filen ./internal/server/user.go
  2. Den faktiske dokumentasjonen ligger i ./openapi3.yaml, og du kan ved en feil glemme å endre den.
  3. Hvis du ikke oppdager dette problemet, sender du en Pull Request og får gjennomgang fra kollegaene.
  4. 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.

  1. Skriv et YAML- eller JSON-dokument i samsvar med OpenAPI-spesifikasjonen
  2. Kjør CLI-en
  3. Tilsvarende Go-stub-kode genereres
  4. 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 😘