GoSuda

Go és az OpenAPI ökoszisztéma

By iwanhae views ...

Bevezetés

Amikor Go nyelven production backend szervert fejlesztünk, szinte minden fejlesztő számára az egyik első nehézség a következő kérdés:

API dokumentálás, hogyan csináljam...?

Ha egy kicsit utánanézünk, hamar rájövünk, hogy az OpenAPI specifikációnak megfelelő dokumentáció készítése előnyös, és természetesen olyan könyvtárakat keresünk, amelyek integrálhatók az OpenAPI-val. Miután azonban ez a döntés megszületett, következik a második probléma:

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

Ez a dokumentum kezdő Go fejlesztők számára készült, akik ebben a helyzetben vannak, és egy rövid könyvtárismertetőt nyújt. A 2024 végi állapot alapján készült, és mivel a nyelvi ökoszisztéma folyamatosan változik, javasoljuk, hogy a dokumentumot használva mindig nézzék meg a legfrissebb híreket is.

Az OpenAPI-hoz való megközelítések a könyvtárakban

Valószínűleg már ismert, hogy az OpenAPI a REST API-k egyértelmű definiálására és dokumentálására szolgáló specifikáció. Az API végpontjait, kéréseit és válaszformátumait YAML vagy JSON formátumban határozza meg, ami nemcsak a fejlesztőknek segít, hanem a frontend és backend kód generálásának automatizálásával csökkenti a felesleges ismétléseket és a kisebb emberi hibákat is.

Az OpenAPI projektbe való természetes integrálása érdekében a Go ökoszisztéma könyvtárai nagyjából három stratégiát követnek.

1. Go kommentek összeállítása OpenAPI specifikációs dokumentummá

Az OpenAPI szerint fejlesztett API-k egyik nehézsége, hogy a tényleges dokumentáció és a megvalósító kód külön fájlokban, egymástól távol helyezkedik el, ezért gyakran előfordul, hogy a kódot frissítik, de a dokumentációt nem, vagy fordítva.

Egy egyszerű példa:

  1. Módosítjuk az API logikáját a ./internal/server/user.go fájlban,
  2. miközben a tényleges dokumentáció a ./openapi3.yaml fájlban található, így könnyen elfelejthetjük ennek módosítását.
  3. Ha nem vesszük észre a problémát, és így nyitunk Pull Requestet, a kollégák felülvizsgálata során
  4. a reviewerek sem látják a ./openapi3.yaml változásait, ezért előfordulhat, hogy az API specifikáció változatlan marad, miközben a tényleges API implementáció megváltozik, ami komoly problémát okozhat.

Ha az API dokumentációt Go kommentek formájában írjuk meg, ez a probléma bizonyos mértékig orvosolható. Mivel a kód és a dokumentáció egy helyen található, a kód módosításakor a kommenteket is egyszerűen frissíthetjük. A kommentek alapján automatikusan OpenAPI specifikációs dokumentumot generáló eszközök is léteznek.

Egy reprezentatív projekt a Swag. A Swag a Go kód kommentjeit feldolgozva OpenAPI 2 formátumú dokumentumot generál. A használata egyszerű: a handler függvény fölé a adott könyvtár által meghatározott formátumban kell kommenteket írni.

 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}

Ha ilyen kommenteket írunk, a Swag nevű CLI feldolgozza ezeket, és OpenAPI 2 dokumentumot készít. Ez a művelet általában a CI folyamatban történik, a generált OpenAPI specifikáció pedig Git repositoryba, a végső build eredményekbe vagy egy külön, külső API-dokumentum-kezelő rendszerbe kerül, és más projektekkel való együttműködés során használják.

Előnyök:

  • Mivel a kommentek a kóddal együtt találhatók, a tényleges kód és a dokumentáció eltérésének valószínűsége csökken.
  • Külön eszköz vagy bonyolult konfiguráció nélkül, csupán kommentekkel egyszerűen és szabadon dokumentálhatunk.
  • Mivel a kommentek nem befolyásolják a tényleges API logikát, jól alkalmazhatók olyan ideiglenes funkciók hozzáadására, amelyeket nem akarunk nyilvánosan dokumentálni.

Hátrányok:

  • A kommentek számának növekedésével a egyetlen kódfájl olvashatósága romolhat.
  • Kommentek formájában nehéz lehet kifejezni az összes API specifikációt.
  • A dokumentum nem kényszeríti ki a kódot, ezért nem garantálható, hogy az OpenAPI dokumentum és a tényleges logika megegyezik.

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

Van olyan megközelítés is, amely a Single Source of Truth (SSOT) forrását nem a Go kódban, hanem a dokumentumban helyezi el. Ez azt jelenti, hogy először az OpenAPI specifikációt definiáljuk, majd ez alapján generálunk Go kódot. Mivel az API specifikáció generálja a kódot, fejlesztési kultúrát tekintve kikényszeríthetjük, hogy először az API tervezés történjen meg, és mivel a specifikáció definiálása a fejlesztés legelején történik, a hiányosságokat korán észrevehetjük, így megelőzhető, hogy a fejlesztés befejezése után, az API specifikáció módosításával együtt az egész kódot át kelljen írni.

Ezt a megközelítést alkalmazó reprezentatív projekt az oapi-codegen és az OpenAPI Generator. A használat egyszerű:

  1. Írjunk YAML vagy JSON dokumentumot az OpenAPI specifikációnak megfelelően,
  2. futtassuk a CLI-t,
  3. és létrejön a megfelelő Go stub kód.
  4. Ezután már csak az egyes API-k részletes logikáját kell megvalósítanunk, 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 az interfész alapján generált kód elvégzi a query paraméterek, a header és a body feldolgozását, a validációt, majd meghívja az interfészben deklarált megfelelő metódust. A felhasználónak csak ezt az interfészt kell implementálnia, és ezzel az API megvalósításhoz szükséges munka elkészül.

Előnyök:

  • Mivel a specifikáció készül el először, és utána kezdődik a fejlesztés, több csapat párhuzamos munkavégzése esetén a feladatok párhuzamosan haladhatnak.
  • A korábban ismétlődő, unalmas munkát igénylő kódrészek automatikusan generálódnak, így a munkahatékonyság nő, miközben a hibakeresés továbbra is könnyű marad.
  • Könnyen biztosítható, hogy a dokumentum és a kód mindig megegyezzen.

Hátrányok:

  • Az OpenAPI specifikáció alapos ismerete nélkül kezdetben meredek lehet a tanulási görbe.
  • Mivel az API-t kezelő kód szerkezetét a projekt automatikusan generálja, egyedi testreszabás esetén nehézkes lehet az alkalmazkodás.

A szerző megjegyzése. 2024 októberi állapot szerint az OpenAPI Generator által generált Go kód nemcsak az API logikát, hanem a teljes projektstruktúrát is kikényszeríti, és a merev szerkezet miatt nem alkalmas arra, hogy a production környezethez szükséges sokféle funkciót hozzáadjunk. Azoknak, akik ezt a megközelítést választják, erősen javasolt az oapi-codegen használata. A szerző az oapi-codegen + echo + StrictServerInterface kombinációt használja.

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

Amikor tucatnyi vagy akár több száz ember fejleszt ugyanazon a szerveren, elkerülhetetlen probléma, hogy az egyes API-k közötti egységesség megbomolhat. Egy szemléletes példa: ha több mint 100 API endpoint specifikációját egyetlen OpenAPI YAML fájlban deklaráljuk, a fájl könnyen tízezer soron felüli szörnyeteggé válik, és új endpoint deklarálásakor elkerülhetetlenül ugyanazt a modellt többször definiáljuk, bizonyos mezőket kihagyunk, vagy a konvencióknak nem megfelelő path elnevezések születnek, így az egész API egységessége megbomlik.

Ennek megoldására külön OpenAPI YAML-tulajdonost is kijelölhetünk, vagy Linter-t fejleszthetünk, amely a CI folyamat során automatikusan felismeri a problémákat. Go nyelven azonban Domain-specific language (DSL) is definiálható, amellyel kikényszeríthető, hogy minden API egységes legyen.

Ezt a technikát alkalmazó reprezentatív projekt a Kubernetes, amely saját magán építi ki a rendszert külön könyvtár nélkül. Használhatjuk emellett a go-restful és a goa projekteket is. Az alábbiakban a goa használatának 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})

Ha ehhez hasonlóan fordítható Go kódot írunk, a POST /users API megvalósítása és dokumentációjának definiálása egyszerre készül el, ami nagy erősség.

Előnyök:

  • Mivel minden a kódból származik, könnyű az API egységességét fenntartani az egész projektben.
  • A Go erős típusrendszerét kihasználva pontosabb és vitathatatlanabb specifikációt kaphatunk, mint az OpenAPI3 összes funkciójának használatakor.

Hátrányok:

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

Összegzés

Minden megközelítésnek vannak előnyei és hátrányai, ezért fontos, hogy a projekt követelményei és a csapat preferenciái alapján válasszuk ki a megfelelő módszert. A legfontosabb nem az, hogy melyik módszer a jobb általában, hanem az, hogy értékelni tudjuk, melyik megoldás a legmegfelelőbb a jelenlegi helyzetünkben, és magas fejlesztési produktivitással elérjük a korai feierabendet és a kiegyensúlyozott munka-magánélet egyensúlyt.

Bár a cikket 2024 októberi állapot szerint írtuk, a Go és az OpenAPI ökoszisztéma folyamatosan fejlődik, ezért kérjük, hogy a cikk olvasásának idejét is figyelembe véve kövessék nyomon a könyvtárak és projektek legfrissebb híreit, valamint azok megváltozott előnyeit és hátrányait.

Boldog Go életet kívánok~ 😘