GoSuda

Go és az OpenAPI ökoszisztéma

By iwanhae
views ...

Bevezetés

A Go nyelven történő Production Backend szerver fejlesztése során szinte minden fejlesztő egyik legelső kihívása a következő:

Hogyan dokumentáljuk az API-kat...?

Ha egy kicsit utánaolvasunk, rájövünk, hogy az OpenAPI specifikációnak megfelelő dokumentumok készítése előnyös, és természetesen az OpenAPI-val integrált könyvtárakat kezdünk keresni. Azonban e döntés meghozatala után is felmerül egy másik probléma:

Sok OpenAPI-val kapcsolatos könyvtár van... Melyiket használjam...?

Ez a dokumentum egy rövid könyvtárbemutató, amelyet a Go-val ismerkedő, hasonló helyzetben lévő fejlesztők számára írtunk. A dokumentum 2024 végén íródott, és mivel a nyelvi ökoszisztéma folyamatosan változik, javasoljuk, hogy a használata közben mindig tájékozódjon a legfrissebb fejleményekről.

Az OpenAPI-t kezelő könyvtárak stratégiái

Mint azt már valószínűleg tudja, az OpenAPI egy specifikáció a REST API-k egyértelmű definiálásához és dokumentálásához. Az API végpontjait, kéréseit, válaszformátumait YAML vagy JSON formátumban definiálja, ezáltal nemcsak a fejlesztők, hanem a frontend és backend kódok generálását is automatizálja, csökkentve az értelmetlen ismétlődést és a kisebb emberi hibákat.

Annak érdekében, hogy az OpenAPI-t zökkenőmentesen integrálják a projektekbe, a Go ökoszisztéma könyvtárai alapvetően háromféle stratégiát alkalmaznak.

1. Go megjegyzések kombinálása OpenAPI specifikációs dokumentummá

Az OpenAPI-nak megfelelő API-k fejlesztése során az egyik legnagyobb kihívás, hogy a tényleges dokumentáció és a dokumentumot megvalósító kód külön fájlokban, teljesen más helyen található. Ez gyakran ahhoz vezet, hogy a kódot frissítik, de a dokumentációt nem, vagy fordítva, a dokumentációt frissítik, de a kódot nem.

Egy egyszerű példa:

  1. A ./internal/server/user.go fájlban módosítjuk az API logikáját.
  2. A tényleges dokumentáció a ./openapi3.yaml fájlban található, és véletlenül elfelejthetjük a módosítást.
  3. Ha nem vesszük észre ezt a változást, és Pull Requestet küldünk, majd a kollégáink átnézik.
  4. A lektorok sem látják a ./openapi3.yaml változásait, ezért előfordulhat, hogy az API specifikáció változatlan marad, de a tényleges API megvalósítás megváltozik.

Az API dokumentáció Go megjegyzésként történő írása részben megoldhatja ezt a problémát. Mivel a kód és a dokumentáció egy helyen van, a kód módosításával együtt a megjegyzéseket is frissíthetjük. Léteznek olyan eszközök, amelyek automatikusan generálnak OpenAPI specifikációs dokumentumokat ezen megjegyzések alapján.

Egy tipikus projekt a Swag. A Swag a Go kód megjegyzéseit elemzi, és OpenAPI 2 formátumú dokumentumot generál. A használata egyszerű: a handler függvények fölé kell írni a megjegyzéseket a könyvtár által meghatározott formátumban.

 1// @Summary Felhasználó létrehozása
 2// @Description Új felhasználó létrehozása.
 3// @Tags Users
 4// @Accept json
 5// @Produce json
 6// @Param user body models.User true "Felhasználói adatok"
 7// @Success 200 {object} models.User
 8// @Failure 400 {object} models.ErrorResponse
 9// @Router /users [post]
10func CreateUser(c *gin.Context) {
11    // ...
12}

A megjegyzések megírása után a Swag CLI elemzi ezeket, és létrehozza az OpenAPI 2 dokumentumot. Általában a CI folyamat során történik meg ez a művelet, a generált OpenAPI specifikációs dokumentum pedig a Git Repository-ban, a végső build eredményben vagy egy külön külső API dokumentumkezelő rendszerben kerül közzétételre, hogy más projektekkel való együttműködés során használható legyen.

Előnyök:

  • Mivel a megjegyzések a kóddal együtt találhatók, csökken annak az esélye, hogy a tényleges kód és a dokumentáció formája eltérjen.
  • Külön eszközök vagy bonyolult beállítások nélkül, csak megjegyzésekkel egyszerűen és szabadon dokumentálhatunk.
  • Mivel a megjegyzések nincsenek hatással a tényleges API logikára, ideálisak a dokumentációban nem szívesen nyilvánosságra hozott ideiglenes funkciók hozzáadásához.

Hátrányok:

  • A megjegyzések sorainak számának növekedésével csökkenhet az egyes kódfájlok olvashatósága.
  • Nehéz lehet a teljes API specifikációt megjegyzések formájában kifejezni.
  • Mivel a dokumentáció nem kényszeríti a kódot, nem garantálható, hogy az OpenAPI dokumentáció és a tényleges logika megegyezik.

2. Go kód generálása OpenAPI specifikációs dokumentumból

Létezik egy olyan módszer is, ahol a Single source of Truth (SSOT) nem a Go kódban, hanem a dokumentációban található. Ez az a megközelítés, amikor először az OpenAPI specifikációt definiáljuk, majd a definiált tartalom alapján Go kódot generálunk. Mivel az API specifikáció közvetlenül kódot generál, ez a fejlesztési kultúrában az API tervezésének előtérbe helyezését kényszeríti ki. A fejlesztési sorrendben az API specifikáció definiálása az első lépés, ezáltal megelőzhető az a kellemetlen helyzet, hogy a fejlesztés befejezése után derül ki, hogy valamit kihagytunk, ami miatt az API specifikációt és a teljes kódot módosítani kell.

Az ezt a módszert alkalmazó tipikus projektek az oapi-codegen és az OpenAPI Generator. A használatuk egyszerű:

  1. Írjuk meg a YAML vagy JSON dokumentumot az OpenAPI specifikációnak megfelelően.
  2. Futtassuk a CLI-t.
  3. Létrejön a megfelelő Go stub kód.
  4. Most már csak az egyes API-k részletes logikáját kell megvalósítani, hogy a stub használhassa azokat.

Az alábbiakban egy példa látható az oapi-codegen által generált kódból:

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}

A fenti interface-t használva az oapi-codegen által generált kód elvégzi a query paraméterek, a headerek, a bodyk elemzését és validálását, majd meghívja az interface-ben deklarált megfelelő metódust. A felhasználónak csak a fenti interface megvalósítását kell megírnia, és az API megvalósításához szükséges munka el is végezhető.

Előnyök:

  • Mivel először a specifikáció készül el, és csak azután kezdődik a fejlesztés, a több csapatban történő együttműködés esetén könnyebb párhuzamosan végezni a munkát.
  • A ismétlődő, monoton munka automatikusan generálódik, így a munka hatékonysága nő, miközben a hibakeresés is könnyebb marad.
  • Könnyen garantálható, hogy a dokumentáció és a kód formája mindig megegyezik.

Hátrányok:

  • Ha az ember nem ismeri az OpenAPI specifikációt, a kezdeti tanulási görbe kissé meredek lehet.
  • Mivel az API-kat kezelő kód formáját a projekt automatikusan generálja, nehéz lehet testreszabni, ha erre szükség van.

A szerző megjegyzése. 2024 októberében az OpenAPI Generator által generált Go kód nemcsak az API logikát, hanem a teljes projekt formáját is erőlteti, és a projekt struktúrája merev, ezért nem megfelelő a tényleges Production környezetben szükséges különféle funkciók hozzáadásához. Azoknak, akik ezt a módszert választják, erősen ajánljuk az oapi-codegen használatát. Én magam az oapi-codegen + echo + StrictServerInterface kombinációt használom.

3. OpenAPI specifikációs dokumentum generálása Go kóddal

Ha több tucat vagy akár több száz ember dolgozik ugyanazon a szerveren, elkerülhetetlenül felmerülhet az a probléma, hogy az egyes API-k egységessége sérülhet. Egy szemléletes példa: ha több mint 100 API végpont specifikációját egyetlen OpenAPI YAML fájlban deklaráljuk, a fájl egy több mint 10 000 soros szörnyeteggé válhat, és az új API végpontok deklarálásakor elkerülhetetlenül előfordulhat, hogy ugyanazokat a modelleket többször deklaráljuk, néhány mezőt kihagyunk, vagy a konvencióknak nem megfelelő elérési utak jönnek létre, ami az API általános egységességének felbomlásához vezet.

A probléma megoldásához kijelölhetünk egy tulajdonost az OpenAPI YAML fájl kezeléséhez, vagy fejleszthetünk egy Lintert, amely a CI folyamat során automatikusan felismeri a problémákat, de a Go nyelv segítségével létrehozhatunk egy Domain-specific language (DSL) nyelvet, amellyel minden API egységes lehet.

A Kubernetes tipikus projekt, amely ezt a technikát használja (saját maga fejlesztette ki, külön könyvtár nélkül). Használhatók még a go-restful és goa projektek is. Az alábbiakban a goa használatára láthatunk egy példát:

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

A fenti, fordítható Go kód megírásával a POST /users API megvalósítása és a dokumentáció definíciója egyidejűleg elkészül, ezáltal előnyöket szerezhetünk.

Előnyök:

  • Mivel mindent a kódból származtatunk, könnyű megőrizni az API-k egységességét a teljes projektben.
  • A Go erős típusrendszerének használatával pontosabb és vitathatatlan specifikációt érhetünk el, mint az OpenAPI3 összes funkciójának használatával.

Hátrányok:

  • Meg kell tanulni az egyes keretrendszerek által meghatározott DSL-t, és nehéz lehet a meglévő kódra alkalmazni.
  • Mivel a keretrendszer által javasolt szabályokat kötelezően be kell tartani, csökkenhet a szabadság és a rugalmasság.

Összegzés

Minden módszernek vannak előnyei és hátrányai, és fontos, hogy a projekt követelményeinek és a csapat preferenciáinak megfelelően válasszuk ki a megfelelő módszert. A legfontosabb kérdés nem az, hogy melyik módszer a legjobb, hanem az, hogy értékeljük a jelenlegi helyzetünket, és a fejlesztési termelékenység növelése érdekében a legmegfelelőbb megoldást válasszuk, hogy hamar végezzünk a munkával, és elégedettek legyünk a munka és a magánélet egyensúlyával.

Bár a cikket 2024 októberében írtam, a Go és az OpenAPI ökoszisztéma folyamatosan fejlődik, ezért kérjük, hogy a cikk elolvasásának időpontját figyelembe véve folyamatosan kövesse nyomon az egyes könyvtárak és projektek újdonságait, valamint azok megváltozott előnyeit és hátrányait.

Boldog Go életet! 😘