Go és az OpenAPI ökoszisztéma
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:
- A
./internal/server/user.go
fájlban módosítjuk az API logikáját. - A tényleges dokumentáció a
./openapi3.yaml
fájlban található, és véletlenül elfelejthetjük a módosítást. - Ha nem vesszük észre ezt a változást, és Pull Requestet küldünk, majd a kollégáink átnézik.
- 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ű:
- Írjuk meg a YAML vagy JSON dokumentumot az OpenAPI specifikációnak megfelelően.
- Futtassuk a CLI-t.
- Létrejön a megfelelő Go stub kód.
- 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! 😘