Go og OpenAPI-økosystemet
Introduksjon
Når man utvikler en Production Backend server med Go, er en av de første utfordringene de fleste utviklere møter, følgende:
Hvordan dokumenterer jeg API-et...?
Etter litt undersøkelser innser man at det er fordelaktig å skrive dokumenter i henhold til OpenAPI-spesifikasjonen, og man finner naturlig nok et bibliotek som integreres med OpenAPI. Men selv etter å ha tatt denne beslutningen, oppstår det neste problemet.
Det finnes mange OpenAPI-relaterte biblioteker... Hvilket skal jeg bruke...?
Dette dokumentet er en kort introduksjon til biblioteker, skrevet for Go-nybegynnere som opplever denne situasjonen. Dokumentet er skrevet per slutten av 2024, og siden språkets økosystem alltid er i endring, anbefales det å bruke dette som referanse og samtidig holde seg oppdatert på de siste nyhetene.
Bibliotekenes strategier for å håndtere OpenAPI
Som du kanskje allerede vet, er OpenAPI en spesifikasjon for tydelig å definere og dokumentere REST API-er. API-ets endepunkter, forespørsler og responsformater defineres i YAML- eller JSON-format, noe som ikke bare hjelper utviklere, men også automatiserer genereringen av kode for frontend og backend, reduserer meningsløs repetisjon og bidrar sterkt til å redusere små menneskelige feil.
For å integrere OpenAPI naturlig i prosjekter, bruker bibliotekene i Go-økosystemet hovedsakelig følgende tre strategier.
1. Kombinere Go-kommentarer til OpenAPI-spesifikasjonsdokumenter
En av de vanskelige tingene med å utvikle API-er i henhold til OpenAPI er at selve dokumentet og koden som implementerer dokumentet, eksisterer som separate filer på helt forskjellige steder. Derfor hender det overraskende ofte at koden oppdateres uten at dokumentet oppdateres, eller at dokumentet oppdateres uten at koden oppdateres.
Et enkelt eksempel:
- Du har endret logikken for et API i filen
./internal/server/user.go
. - Selve dokumentet eksisterer i
./openapi3.yaml
, og du kan ved et uhell glemme å gjøre endringer der. - Hvis du ikke er klar over dette problemet og sender en Pull Request for å få tilbakemeldinger fra kolleger,
- vil anmelderne heller ikke se endringene i
./openapi3.yaml
, og dermed kan API-implementeringen endres mens API-spesifikasjonen forblir den samme.
Å skrive API-dokumentasjon i form av Go-kommentarer kan løse dette problemet til en viss grad. Siden kode og dokumentasjon er samlet på ett sted, kan du oppdatere kommentarene samtidig som du endrer koden. Det finnes verktøy som automatisk genererer OpenAPI-spesifikasjonsdokumenter basert på disse kommentarene.
Et representativt prosjekt er Swag. Swag parser Go-kodekommentarer og genererer dokumenter i OpenAPI 2-format. Det er enkelt å bruke. Bare skriv kommentarer i henhold til formatet som er definert i hvert bibliotek, over handler-funksjonen.
1// @Summary Opprett bruker
2// @Description Oppretter en ny bruker.
3// @Tags Users
4// @Accept json
5// @Produce json
6// @Param user body models.User true "Brukerinformasjon"
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 på denne måten, vil Swag CLI parse disse kommentarene og generere et OpenAPI 2-dokument. Vanligvis utføres denne operasjonen i CI-prosessen, og det genererte OpenAPI-spesifikasjonsdokumentet distribueres til Git Repository, det endelige byggeproduktet eller et separat eksternt API-dokumentstyringssystem for bruk i samarbeid med andre prosjekter.
Fordeler:
- Siden kommentarene er sammen med koden, er det mindre sannsynlig at den faktiske koden og dokumentasjonens form vil endre seg.
- Du kan enkelt og fritt dokumentere med bare kommentarer, uten separate verktøy eller kompliserte innstillinger.
- Siden kommentarer ikke påvirker den faktiske API-logikken, er det fint å legge til midlertidige funksjoner som er vanskelige å publisere som dokumentasjon.
Ulemper:
- Lesbarheten til en enkelt kodefil kan reduseres ettersom antall kommentarlinjer øker.
- Det kan være vanskelig å uttrykke alle API-spesifikasjoner i form av kommentarer.
- Siden dokumentasjonen ikke tvinger koden, kan man ikke garantere at OpenAPI-dokumentet samsvarer med den faktiske logikken.
2. Generere Go-kode fra OpenAPI-spesifikasjonsdokumenter
Det finnes også en metode for å plassere Single source of Truth (SSOT) på dokumentsiden i stedet for Go-koden. Det vil si å definere OpenAPI-spesifikasjonen først og generere Go-kode basert på det definerte innholdet. Siden API-spesifikasjonen umiddelbart genererer kode, kan man tvinge frem en utviklingskultur der API-design gjøres først. Siden defineringen av API-spesifikasjonen er det første som starter i utviklingssekvensen, har den den fordelen at den kan forhindre ulykker på et tidlig tidspunkt der man innser en oversettelse først etter at utviklingen er fullført, og hele koden endres sammen med API-spesifikasjonsendringen.
Representantprosjekter som bruker denne metoden inkluderer oapi-codegen og OpenAPI Generator. Bruken er enkel.
- Skriv et yaml- eller json-dokument i henhold til OpenAPI-spesifikasjonen og
- kjør CLI.
- Go stub-koden som tilsvarer den, genereres.
- Nå trenger du bare å implementere den detaljerte logikken for hvert API direkte, slik at denne stubben kan brukes.
Følgende er et eksempel på koden som genereres av oapi-codegen.
1// StrictServerInterface represents all server handlers.
2// StrictServerInterface representerer alle server-behandlere.
3type StrictServerInterface interface {
4 // ...
5 // Returns all pets
6 // Returnerer alle kjæledyr
7 // (GET /pets)
8 FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
9 // ...
10}
Koden som genereres av oapi-codegen via grensesnittet ovenfor, utfører logikk som spørringsparametere, header, kroppsparsing og validering, og kaller den passende metoden som er deklarert i grensesnittet. Brukeren trenger bare å implementere implementeringen for grensesnittet ovenfor for å fullføre arbeidet som kreves for API-implementering.
Fordeler:
- Siden spesifikasjonen kommer først og utviklingen pågår, er det fordelaktig å utføre oppgaver parallelt i tilfeller der flere team samarbeider.
- Koden for delen som ble utført som repetitivt slitearbeid genereres automatisk, noe som øker arbeidseffektiviteten og likevel er fortsatt fordelaktig for feilsøking.
- Det er lett å garantere at formen på dokumentet og koden alltid samsvarer.
Ulemper:
- Det er en viss innledende læringskurve hvis du er uvitende om selve OpenAPI-spesifikasjonen.
- Siden formen på koden som håndterer API-et genereres automatisk av prosjektet, kan det være vanskelig å svare hvis tilpasning er nødvendig.
Forfatterens kommentar. Per oktober 2024 tvinger Go-koden som genereres av OpenAPI Generator frem ikke bare API-logikk, men også hele prosjektformen, og strukturen til prosjektet er stiv, så det genererer kode av en form som er uegnet for å legge til ulike funksjoner som kreves i et faktisk produksjonsmiljø. Hvis du bruker denne metoden, anbefaler vi på det sterkeste at du bruker oapi-codegen. Forfatteren bruker oapi-codege + echo + StrictServerInterface.
3. Generere OpenAPI-spesifikasjonsdokumenter med Go-kode
En utfordring som uunngåelig oppstår når dusinvis eller hundrevis av mennesker utvikler den samme serveren, er at enhetligheten kan brytes ned for hvert API. Som et intuitivt eksempel, hvis spesifikasjonene for mer enn 100 API-endepunkter deklareres i én OpenAPI yaml-fil, vil filen være et monster på mer enn 10 000 linjer, og når et nytt API-endepunkt deklareres, vil det samme Modellen dupliseres uunngåelig eller noen få felt utelates, eller navngivning av stier som ikke er i samsvar med konvensjonen, vil bli født, og hele API-ets enhetlighet vil begynne å brytes ned.
For å løse dette problemet kan du angi en separat eier for å administrere OpenAPI yaml, eller utvikle en Linter for å automatisk fange opp dette under CI-prosessen, men du kan definere et domenespesifikt språk (DSL) i Go-språket for å tvinge alle API-er til å ha konsistent enhetlighet.
Et representativt prosjekt som bruker denne teknikken er Kubernetes (bygget internt uten et separat bibliotek), og du kan bruke prosjekter som go-restful, 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 du skriver kompilerbar Go-kode som ovenfor, kan du få fordelen av å fullføre implementeringen og dokumentasjonsdefinisjonen for POST /users
API-et samtidig.
Fordeler:
- Siden alt kommer fra koden, er det lett å opprettholde API-konsistens for hele prosjektet.
- Ved å bruke Go sitt sterkt typede system kan du få en mer nøyaktig og ikke-kontroversiell spesifikasjon enn når du bruker alle funksjonene i OpenAPI3.
Ulemper:
- Du må lære DSL definert i hvert rammeverk, og det kan være vanskelig å bruke på eksisterende kode.
- Siden du må følge reglene som foreslås av rammeverket tvangsmessig, kan frihet og fleksibilitet reduseres.
Avslutter
Hver metode har sine fordeler og ulemper, og det er viktig å velge riktig metode i henhold til prosjektets krav og teamets preferanser. Det viktigste er alltid å vurdere hva som er den mest passende løsningen for din nåværende situasjon, i stedet for å bruke en bestemt metode, og øke utviklingsproduktiviteten for å nyte en tidlig arbeidsdags slutt og en tilfredsstillende balanse mellom arbeid og fritid.
Jeg har skrevet dette innlegget per oktober 2024, men Go- og OpenAPI-økosystemene er i stadig utvikling, så vær oppmerksom på gapet mellom tidspunktet du leser dette innlegget og de nyeste nyhetene om hvert bibliotek og prosjekt, og deres endrede fordeler og ulemper.
Ha et lykkelig Go-liv~ 😘