Go és az OpenAPI ökoszisztéma

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:
- Módosítjuk az API logikáját a
./internal/server/user.gofájlban, - miközben a tényleges dokumentáció a
./openapi3.yamlfájlban található, így könnyen elfelejthetjük ennek módosítását. - Ha nem vesszük észre a problémát, és így nyitunk Pull Requestet, a kollégák felülvizsgálata során
- a reviewerek sem látják a
./openapi3.yamlvá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ű:
- Írjunk YAML vagy JSON dokumentumot az OpenAPI specifikációnak megfelelően,
- futtassuk a CLI-t,
- és létrejön a megfelelő Go stub kód.
- 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~ 😘