GoSuda

Go és az OpenAPI ökoszisztéma

By iwanhae
views ...

Bevezetés

Amikor Production Backend szervert fejlesztünk Go nyelven, az egyik első probléma, amivel a legtöbb fejlesztő találkozik, a következő:

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

Rövid kutatás után rájön az ember, hogy előnyös az OpenAPI specifikációnak megfelelő dokumentáció elkészítése, és természetesen elkezd keresni egy olyan könyvtárat, amely integrálható az OpenAPI-val. Azonban még e döntés meghozatala után is felmerül a következő probléma:

Sok OpenAPI-hoz kapcsolódó könyvtár van... melyiket használjam...?

Ez a dokumentum egy rövid könyvtárbemutató azoknak a Go kezdőknek, akik hasonló helyzetben vannak. A dokumentum 2024 végén készült, és mivel a nyelvi ökoszisztéma mindig dinamikusan változik, javasolt a legfrissebb információk figyelembe vétele és nyomon követése.

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

Ahogy már valószínűleg tudja, az OpenAPI egy specifikáció a REST API-k egyértelmű meghatározására és dokumentálására. Az API végpontok, kérések és válaszformátumok YAML vagy JSON formátumban történő meghatározásával nemcsak a fejlesztők, hanem a frontend és backend kódgenerálás automatizálását is segíti, csökkentve az értelmetlen ismétléseket és a kisebb emberi hibákat.

Az OpenAPI projektbe való zökkenőmentes integrálásához a Go ökoszisztéma könyvtárai alapvetően a következő három stratégiát alkalmazzák:

1. Go kommentek kombinálása OpenAPI specifikációs dokumentummá

Az OpenAPI-val összhangban lévő API fejlesztésének egyik nehézsége, hogy a tényleges dokumentáció és az azt megvalósító kód külön fájlokban, teljesen eltérő helyeken található, így gyakran előfordul, hogy a kód frissítésekor elfelejtik frissíteni a dokumentációt, vagy fordítva.

Egy egyszerű példa:

  1. Módosítjuk az API logikáját a ./internal/server/user.go fájlban, de
  2. a tényleges dokumentáció a ./openapi3.yaml fájlban található, és a változtatást véletlenül elfelejthetjük.
  3. Ha nem vesszük észre ezt a változással kapcsolatos problémát, és Pull Requestet küldünk be, majd kollégáktól kapunk véleményezést,
  4. a véleményezők sem látják a ./openapi3.yaml fájlban történt változásokat, így előfordulhat, hogy az API specifikáció változatlan marad, miközben a tényleges API implementáció megváltozik.

Az API dokumentáció Go kommentek formájában történő megírása bizonyos mértékben enyhítheti ezt a problémát. Mivel a kód és a dokumentáció egy helyen található, a kódot módosítva a kommenteket is frissíthetjük. Léteznek olyan eszközök, amelyek ezen kommentek alapján automatikusan generálnak OpenAPI specifikációs dokumentumokat.

A 대표적인 projekt a Swag. A Swag a Go kód kommentjeit elemzi, és OpenAPI 2 formátumú dokumentumot generál. Használata egyszerű: a handler függvények fölé kell írni a kommenteket az adott könyvtárban meghatározott formátum szerint.

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

Ha így írjuk meg a kommenteket, a Swag nevű CLI elemzi ezeket a kommenteket, és OpenAPI 2 dokumentumot generál. Általában a CI folyamat során történik ez a munka, és a generált OpenAPI specifikációs dokumentumot Git Repositoryba, a végső build eredményeként, vagy különálló külső API dokumentációkezelő rendszerbe telepítik, hogy más projektekkel való együttműködés során felhasználható legyen.

Előnyök:

  • Mivel a kommentek a kóddal együtt vannak, csökken annak az esélye, hogy a tényleges kód és a dokumentáció eltérő legyen.
  • Külön eszközök vagy bonyolult beállítások nélkül, pusztán kommentekkel egyszerűen és szabadon dokumentálható.
  • Mivel a kommentek nem befolyásolják a tényleges API logikát, könnyen hozzáadhatók olyan ideiglenes funkciók, amelyeket nehéz lenne nyilvánosan dokumentálni.

Hátrányok:

  • A kommentek sorainak növekedésével csökkenhet egyetlen kódfájl olvashatósága.
  • Az összes API specifikációt nehéz lehet kommentek 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 egyezik.

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

Létezik egy módszer, amely a Single Source of Truth (SSOT) szerepét nem a Go kódnak, hanem a dokumentációnak adja. Ez az OpenAPI specifikáció előzetes definiálása, majd a definiált tartalom alapján Go kód generálása. Mivel az API specifikáció közvetlenül generálja a kódot, a fejlesztési kultúrában kikényszeríthető az API tervezésének elsőbbsége, és a fejlesztési sorrendben az API specifikáció definiálása az első lépés, ami előnyt jelent abban, hogy még a fejlesztés befejezése előtt felismerhetők a hiányzó részek, és elkerülhetők a későbbi API specifikáció változásokkal és a teljes kód módosításával járó problémák.

Ezt a megközelítést alkalmazó 대표적인 projektek az oapi-codegen és az OpenAPI Generator. A használatuk egyszerű:

  1. Létrehozzuk a yaml vagy json dokumentumot az OpenAPI specifikációnak megfelelően,
  2. futtatjuk a CLI-t,
  3. ekkor létrejön a megfelelő Go stub kód.
  4. Ezután már csak az egyedi API-k részletes logikáját kell implementálni, hogy a stub használható legyen.

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

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}

Az oapi-codegen által generált kód a fenti interface-en keresztül query paraméterek, fejlécek, body elemzés és Validation logikát hajt végre, majd meghívja az interface-ben deklarált megfelelő metódust. A felhasználónak csak az interface implementációját kell elkészítenie, és az API implementációjához szükséges munka befejeződik.

Előnyök:

  • Mivel a specifikáció előbb készül el, majd utána kezdődik a fejlesztés, több csapat közötti együttműködés esetén előnyös a párhuzamos munkavégzés.
  • Mivel az ismétlődő, kézi munkával végzett részek kódja automatikusan generálódik, növekszik a munka hatékonysága, miközben továbbra is előnyös a hibakeresés szempontjából.
  • Könnyen garantálható, hogy a dokumentáció és a kód formája mindig egyezik.

Hátrányok:

  • Ha az OpenAPI specifikációval kapcsolatban nincsenek ismeretek, akkor kezdetben jelentős tanulási görbe tapasztalható.
  • Mivel az API-t kezelő kód formája automatikusan generálódik a projekt által, nehéz lehet alkalmazkodni, ha testreszabásra van szükség.

A szerző megjegyzése. 2024. októberi állapot szerint az OpenAPI Generator által generált Go kód nemcsak az API logikát, hanem az egész projekt formáját is kikényszeríti, és a projekt struktúrája merev, így nem alkalmas a Production környezetben szükséges különféle funkciók hozzáadására. Azoknak, akik ezt a módszert választják, erősen javaslom az oapi-codegen használatát. A szerző az oapi-codege + echo + StrictServerInterface kombinációt használja.

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

Amikor több tucat, vagy akár több száz ember dolgozik ugyanazon a szerveren, elkerülhetetlenül felmerül a probléma, hogy az egyes API-k közötti konzisztencia megszakadhat. Egy intuitív példával élve, ha több mint 100 API végpont specifikációját egyetlen OpenAPI yaml fájlban deklaráljuk, az a fájl egy 10 000 sort meghaladó szörnnyé válik, és egy új API végpont deklarálásakor elkerülhetetlenül előfordulhat, hogy ugyanazt a modellt duplikáljuk, néhány mező kimarad, vagy a konvencióknak nem megfelelő Path elnevezés jön létre, ami az API általános konzisztenciájának megbomlásához vezet.

Ezen problémák megoldására külön tulajdonost jelölhetünk ki az OpenAPI yaml fájl kezelésére, vagy fejleszthetünk egy Lintert, amely automatikusan észleli a hibákat a CI folyamat során. Azonban a Go nyelvvel Domain-specific language (DSL) definiálásával kikényszeríthető, hogy minden API konzisztens legyen.

Ezt a technikát használó 대표적인 projekt a Kubernetes (külön könyvtár nélkül, saját fejlesztésű), és használhatóak olyan projektek is, mint a go-restful, goa. Az alábbiakban a goa használatának egy példája látható.

 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 fentihez hasonlóan fordítható Go kód megírásával egyidejűleg elérhető a POST /users API implementációjának és dokumentációjának definiálásának előnye.

Előnyök:

  • Mivel minden a kódból származik, könnyen fenntartható a projekt egészére vonatkozó API konzisztencia.
  • A Go erősen típusos rendszerét kihasználva, az OpenAPI3 összes funkciójának használatakor pontosabb és egyértelműbb specifikáció érhető el.

Hátrányok:

  • Meg kell tanulni az egyes keretrendszerek által definiált DSL-t, és nehéz lehet a meglévő kódra alkalmazni.
  • Mivel a keretrendszer által javasolt szabályokat kényszerűen követni kell, csökken a szabadság és a rugalmasság.

Összefoglalásul

Minden módszernek vannak előnyei és hátrányai, és fontos, hogy a projekt igényeinek és a csapat preferenciáinak megfelelően válasszuk ki a megfelelő módszert. Mindig az a legfontosabb, hogy ne az legyen a kérdés, melyik módszer a legjobb, hanem hogy melyik megoldás a legmegfelelőbb a jelenlegi helyzetre, és hogy hogyan lehet magasabb fejlesztési termelékenységet elérni a korai hazamenetel és a kielégítő munka-magánélet egyensúly élvezetéhez.

Bár a bejegyzés 2024. októberi állapot szerint íródott, a Go és az OpenAPI ökoszisztéma folyamatosan fejlődik, ezért javasolt figyelembe venni az olvasás időpontja és a bejegyzés írásának időpontja közötti különbséget, és folyamatosan nyomon követni az egyes könyvtárak és projektek aktuális állapotát, valamint a megváltozott előnyeiket és hátrányaikat.

Kellemes Go életet! 😘