GoSuda

Go ja OpenAPI-ekosysteemi

By iwanhae views ...

Johdanto

Kun kehität Go-kielellä tuotantotason backend-palvelinta, lähes jokainen kehittäjä kohtaa ensimmäisten haasteiden joukossa seuraavan kysymyksen:

API-dokumentointi, miten tämä tehdään...?

Hieman tutkittuaan tähän kysymykseen, kehittäjä huomaa, että OpenAPI-speksin mukaisen dokumentaation laatiminen on hyödyllistä, ja luonnollisesti hän alkaa etsiä OpenAPI:n kanssa yhteensopivia kirjastoja. Tämänkin päätöksen jälkeen kuitenkin seuraa seuraava ongelma.

OpenAPI-aiheisia kirjastoja on paljon.. mitä niistä pitäisi käyttää...?

Tämä dokumentti on lyhyt kirjastoesittely, joka on laadittu Go-aloittelijoille, jotka kohtaavat tämänkaltaisia tilanteita. Dokumentti on kirjoitettu joulukuun 2024 lopun tilanteen mukaisesti. Koska kielen ekosysteemi muuttuu jatkuvasti, suosittelen käyttämään tätä dokumenttia viitteenä ja seuraamaan myös uusimpia kehityksiä.

Kirjastojen strategiat OpenAPI:n käsittelyyn

Kuten varmasti jo tiedätte, OpenAPI on speksi, jonka tarkoituksena on määritellä ja dokumentoida REST API selkeästi. Määrittelemällä API:n endpointit, pyynnöt ja vastausmuodot YAML- tai JSON-muodossa voidaan automatisoida sekä kehittäjien että front-end- ja back-end-koodin generointi. Tämä vähentää turhaa toistoa ja auttaa vähentämään pieniä inhimillisiä virheitä merkittävästi.

Jotta OpenAPI voidaan liittää luonnollisesti projektiin, Go-ekosysteemin kirjastot noudattavat pääasiassa seuraavia kolmea strategiaa.

1. Go-kommentit kootaan OpenAPI-speksidokumentiksi

Yksi OpenAPI:n mukaisen API-kehityksen haastavista puolista on se, että varsinainen dokumentti ja sen toteuttava koodi sijaitsevat erillisissä tiedostoissa täysin eri paikoissa. Tämän vuoksi on yllättävän yleistä, että koodi päivitetään mutta dokumenttia ei, tai dokumentti päivitetään mutta koodia ei.

Yksinkertainen esimerkki:

  1. API:n logiikkaa muokataan tiedostossa ./internal/server/user.go
  2. Varsinainen dokumentti sijaitsee tiedostossa ./openapi3.yaml, ja sen päivittäminen voi unohtua vahingossa.
  3. Jos muutoksesta ei ole tietoa ja Pull Request lähetetään kollegoiden arvioitavaksi,
  4. myös arvioijat eivät näe muutoksia tiedostossa ./openapi3.yaml, joten on mahdollista, että API-speksi pysyy ennallaan, mutta API:n todellinen toteutus muuttuu, mikä on ongelmallista.

Kun API-dokumentaatio kirjoitetaan Go-kommentteina, tätä ongelmaa voidaan lieventää merkittävästi. Koska koodi ja dokumentaatio ovat samassa paikassa, kommentit voidaan päivittää samalla kun koodia muokataan. Tämän perusteella on olemassa työkaluja, jotka generoivat OpenAPI-speksidokumentin automaattisesti kommenteista.

Tunnettu projekti tähän on Swag. Swag jäsentää Go-koodin kommentit ja generoi niistä OpenAPI 2 -muotoisen dokumentin. Käyttö on yksinkertaista: kirjoitetaan käsittelijäfunktion ylle kommentit kyseisen kirjaston määrittelemässä muodossa.

 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}

Kun kommentit on kirjoitettu näin, Swag-CLI jäsentää ne ja generoi OpenAPI 2 -dokumentin. Yleensä tämä vaihe suoritetaan CI-prosessissa, ja generoitu OpenAPI-speksi julkaistaan Git-repositorioon, lopulliseen build-tulokseen tai erilliseen ulkoiseen API-dokumentaationhallintajärjestelmään, jotta muut projektit voivat käyttää sitä yhteistyössä.

Edut:

  • Kommentit ovat koodin yhteydessä, joten todellisen koodin ja dokumentaation muodon eroavaisuuden todennäköisyys pienenee.
  • Ilman erillisiä työkaluja tai monimutkaisia asetuksia, pelkillä kommenteilla voi dokumentoida helposti ja joustavasti.
  • Kommentit eivät vaikuta varsinaiseen API-logiikkaan, joten väliaikaisten ominaisuuksien lisääminen, joiden julkistaminen dokumentaatiossa on arveluttavaa, on helppoa.

Haitat:

  • Kommenttirivien määrän kasvaessa yksittäisen kooditiedoston luettavuus voi heikentyä.
  • Kaikkien API-speksien ilmaiseminen kommenttien muodossa voi olla vaikeaa.
  • Dokumentaatio ei pakota koodia noudattamaan sitä, joten OpenAPI-dokumentin ja todellisen logiikan yhtenevyyttä ei voida taata.

2. Go-koodin generointi OpenAPI-speksidokumentista

On myös mahdollista pitää Single Source of Truth (SSOT) dokumentin puolella eikä Go-koodissa. Tässä lähestymistavassa OpenAPI-speksi määritellään ensin, ja Go-koodi generoidaan määritellyn sisällön perusteella. Koska API-speksi generoi koodin, API-suunnittelu voidaan pakottaa tekemään ensin, ja koska speksin määrittely aloitetaan ennen muuta kehitystä, huomatut puutteet voidaan havaita aikaisessa vaiheessa. Näin vältetään tilanne, jossa koko koodi joudutaan muuttamaan API-speksin muutosten yhteydessä vasta kehityksen päätyttyä.

Tämän lähestymistavan käyttämiä tunnettuja projekteja ovat oapi-codegen ja OpenAPI Generator. Käyttö on yksinkertaista:

  1. Kirjoitetaan OpenAPI-speksin mukainen YAML- tai JSON-dokumentti
  2. Suoritetaan CLI
  3. Siihen vastaava Go stub -koodi generoituu
  4. Tämän jälkeen riittää toteuttaa yksittäisten API-endpointien varsinainen logiikka stubin pohjalta.

Seuraavassa on esimerkki oapi-codegen-työkalun generoimasta koodista.

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}

Generoitu koodi käyttää yllä olevaa interfacea perustana ja suorittaa query-parametrien, headerien ja bodyn jäsentämisen sekä validoinnin, minkä jälkeen se kutsuu interfacessa määriteltyä sopivaa metodia. Käyttäjän tehtäväksi jää ainoastaan toteuttaa tämän interfacen implementaatio, jolloin API:n toteutukseen vaadittava työ on valmis.

Edut:

  • Speksi syntyy ensin ja kehitys etenee sen pohjalta, joten useiden tiimien yhteistyössä työn tekeminen rinnakkain on helpompaa.
  • Koodi, joka aiemmin kirjoitettiin toistuvana ja raskaana työnä, generoituu automaattisesti. Tämä nostaa työn tehokkuutta ja säilyttää hyvän debugattavuuden.
  • Dokumentin ja koodin yhtenevyys on helpompi taata.

Haitat:

  • Jos OpenAPI-speksi on tuntematon, alkuvaiheen oppimiskäyrä voi olla melko jyrkkä.
  • Koska API:n käsittelykoodin rakenne generoituu projektityökalun mukaan, räätälöinti voi olla vaikeaa silloin, kun sitä tarvitaan.

Kirjoittajan kommentti. Lokakuussa 2024 OpenAPI Generatorin generoima Go-koodi pakottaa koko projektin rakenteen eikä vain API-logiikkaa. Rakenne on jäykkä, joten se soveltuu huonosti tuotantoympäristön vaatimien monipuolisten toimintojen lisäämiseen. Niille, jotka valitsevat tämän lähestymistavan, suosittelen lämpimästi oapi-codegenin käyttöä. Itse käytän oapi-codegen + echo + StrictServerInterface -yhdistelmää.

3. OpenAPI-speksidokumentin generointi Go-koodista

Kun kymmenet tai sadat ihmiset kehittävät samaa palvelinta, väistämättömänä ongelmana on, että yhtenäisyys eri API-endpointien välillä voi rikkoutua. Havainnollistavana esimerkkinä, jos yli sadan API-endpointin määrittely kirjoitetaan yhteen OpenAPI YAML -tiedostoon, tiedostosta tulee helposti yli 10 000 rivin hirviö. Uutta endpointia lisättäessä sama malli saatetaan väistämättä määritellä uudelleen, joitakin kenttiä voi jäädä pois tai polkujen nimeäminen voi poiketa sovitusta konventiosta. Tämän seurauksena API:n kokonaisvaltainen yhtenäisyys alkaa murtua.

Tämän ongelman ratkaisemiseksi voidaan nimetä OpenAPI YAML -tiedoston omistaja tai kehittää linter, joka havaitsee ongelmat automaattisesti CI-prosessissa. Go-kielellä voidaan kuitenkin myös määritellä Domain-specific language (DSL), jonka avulla kaikki API:t saadaan pakotettua yhtenäisiksi.

Tämän tekniikan käyttämä tunnettu projekti on Kubernetes, joka toteuttaa sen itse ilman erillistä kirjastoa. Lisäksi voidaan käyttää projekteja kuten go-restful ja goa. Seuraavassa on esimerkki goa-kirjaston käytöstä.

 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})

Kun kirjoitetaan tällainen käännettävissä oleva Go-koodi, saavutetaan etu, että POST /users -API:n toteutus ja sen dokumentaation määrittely valmistuvat samanaikaisesti.

Edut:

  • Kaikki syntyy koodista, joten API:n yhtenäisyyden ylläpitäminen koko projektissa on helppoa.
  • Go:n vahvaa tyyppijärjestelmää hyödyntämällä voidaan saavuttaa tarkempi ja kiistattomampi speksi kuin silloin, kun käytetään kaikkia OpenAPI3:n ominaisuuksia.

Haitat:

  • Jokaisen kehyksen määrittelemä DSL on opittava, ja sen soveltaminen olemassa olevaan koodiin voi olla vaikeaa.
  • Koska kehyksen ehdottamia sääntöjä on noudatettava pakollisesti, joustavuus ja vapaus voivat heiketä.

Lopuksi

Jokaisella menetelmällä on omat etunsa ja haittansa, ja on tärkeää valita projektin vaatimuksiin ja tiimin mieltymyksiin sopiva menetelmä. Kaikkein tärkeintä ei ole se, mikä menetelmä on paras yleisesti, vaan se, mikä ratkaisu sopii parhaiten omaan tilanteeseen. Arvioi kriittisesti, mikä on juuri sinulle sopiva ratkaisu, pidä kehitystuottavuus korkealla, ja nauti nopeasta kotiinlähdöstä ja tyydyttävästä työn ja vapaa-ajan tasapainosta.

Tämä kirjoitus on laadittu lokakuun 2024 tilanteen mukaan, mutta Go- ja OpenAPI-ekosysteemit kehittyvät jatkuvasti. Ota siis huomioon tämän lukemisajankohdan ja kirjoitusajankohdan välinen aikaero, ja seuraa myös jatkossa kirjastojen ja projektien kehitystä sekä niiden muuttuneita etuja ja haittoja.

Hauskaa Go-elämää~ 😘