Go a ekosystém OpenAPI
Úvod
Pri vývoji produkčného backend servera v jazyku Go sa takmer všetci vývojári stretávajú s jednou z prvých výziev:
Ako dokumentovať API...?
Po krátkom hľadaní si uvedomia, že je výhodné vytvárať dokumentáciu podľa špecifikácie OpenAPI, a prirodzene začnú hľadať knižnice, ktoré s OpenAPI spolupracujú. Aj po tomto rozhodnutí však nastáva ďalší problém:
Existuje veľa knižníc súvisiacich s OpenAPI... Ktorú použiť...?
Tento dokument je stručný úvod do knižníc napísaný pre začiatočníkov v Go, ktorí sa ocitli v takejto situácii. Bol napísaný koncom roka 2024 a keďže sa jazykový ekosystém neustále mení, odporúčame vám, aby ste ho brali ako referenciu a zároveň sledovali aktuálny vývoj.
Stratégie knižníc pri práci s OpenAPI
Ako už pravdepodobne viete, OpenAPI je špecifikácia na jednoznačné definovanie a dokumentovanie REST API. Definuje koncové body API, formáty požiadaviek a odpovedí vo formáte YAML alebo JSON, čím pomáha nielen vývojárom, ale aj frontendovým a backendovým tímom automatizovať generovanie kódu, čím sa znižuje bezvýznamné opakovanie a minimalizujú sa drobné ľudské chyby.
Na bezproblémovú integráciu OpenAPI do projektov, knižnice v ekosystéme Go používajú v zásade tri stratégie.
1. Zlučovanie Go komentárov do dokumentu špecifikácie OpenAPI
Jedným z náročných aspektov vývoja API podľa OpenAPI je, že skutočná dokumentácia a kód implementujúci túto dokumentáciu sa nachádzajú v samostatných súboroch na úplne odlišných miestach. Preto sa pomerne často stáva, že sa aktualizuje kód, ale dokumentácia nie, alebo naopak, aktualizuje sa dokumentácia, ale kód nie.
Jednoduchý príklad:
- V súbore
./internal/server/user.go
ste upravili logiku API. - Skutočná dokumentácia sa nachádza v
./openapi3.yaml
a vy ste zabudli na zmenu. - Ak o tejto zmene neviete a odošlete Pull Request, ktorý má byť skontrolovaný kolegami,
- recenzenti tiež neuvidia zmeny v
./openapi3.yaml
, takže môže nastať nepríjemná situácia, keď sa zmení implementácia skutočného API, ale špecifikácia API zostane rovnaká.
Ak napíšete dokumentáciu API vo forme komentárov v Go, tento problém sa dá do istej miery vyriešiť. Keďže kód a dokumentácia sú na jednom mieste, môžete pri úprave kódu aktualizovať aj komentáre. Existujú nástroje, ktoré automaticky generujú dokumentáciu špecifikácie OpenAPI na základe týchto komentárov.
Reprezentatívnym projektom je Swag. Swag analyzuje komentáre v kóde Go a generuje dokumenty formátu OpenAPI 2. Použitie je jednoduché. Stačí napísať komentáre vo formáte definovanom každou knižnicou nad funkciou obsluhy.
1// @Summary Vytvorenie používateľa
2// @Description Vytvorí nového používateľa.
3// @Tags Používatelia
4// @Accept json
5// @Produce json
6// @Param user body models.User true "Informácie o používateľovi"
7// @Success 200 {object} models.User
8// @Failure 400 {object} models.ErrorResponse
9// @Router /users [post]
10func CreateUser(c *gin.Context) {
11 // ...
12}
Ak napíšete komentáre týmto spôsobom, Swag CLI tieto komentáre analyzuje a vygeneruje dokument OpenAPI 2. Táto operácia sa zvyčajne vykonáva v rámci procesu CI a vygenerovaná dokumentácia špecifikácie OpenAPI sa nasadzuje do úložiska Git, do finálneho výstupu zostavenia alebo do samostatného externého systému správy dokumentácie API a používa sa pri spolupráci s inými projektmi.
Výhody:
- Keďže sú komentáre spolu s kódom, znižuje sa možnosť, že sa skutočný kód a dokumentácia budú líšiť.
- Dokumentáciu môžete jednoducho a voľne vytvárať len pomocou komentárov bez potreby ďalších nástrojov alebo zložitých nastavení.
- Keďže komentáre nemajú vplyv na skutočnú logiku API, je vhodné pridávať dočasné funkcie, ktoré nie je vhodné zverejňovať v dokumentácii.
Nevýhody:
- S rastúcim počtom riadkov komentárov sa môže znížiť čitateľnosť jedného súboru kódu.
- Je náročné vyjadriť celú špecifikáciu API vo forme komentárov.
- Keďže dokumentácia nenúti kód, nie je možné zaručiť, že dokumentácia OpenAPI a skutočná logika sú totožné.
2. Generovanie kódu Go z dokumentu špecifikácie OpenAPI
Existuje aj spôsob, ako umiestniť Single source of Truth (SSOT) nie do kódu Go, ale do dokumentácie. Ide o spôsob, ako najprv definovať špecifikáciu OpenAPI a na základe definovaného obsahu generovať kód Go. Keďže špecifikácia API priamo generuje kód, je možné kultúrne vynútiť najprv návrh API a keďže definovanie špecifikácie API je prvým krokom, je možné včas zabrániť nepríjemnostiam, ako je uvedomenie si vynechaných častí až po dokončení vývoja a následná úprava celého kódu spolu so zmenou špecifikácie API.
Reprezentatívnymi projektmi, ktoré tento prístup používajú, sú oapi-codegen a OpenAPI Generator. Použitie je jednoduché.
- Napíšte dokument yaml alebo json podľa špecifikácie OpenAPI.
- Spustite CLI.
- Vygeneruje sa zodpovedajúci kód Go stub.
- Teraz stačí priamo implementovať podrobnú logiku pre jednotlivé API, aby bolo možné tento stub používať.
Nasleduje príklad kódu, ktorý vygeneruje oapi-codegen.
1// StrictServerInterface predstavuje všetky obsluhy servera.
2type StrictServerInterface interface {
3 // ...
4 // Vracia všetky domáce zvieratá
5 // (GET /pets)
6 FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
7 // ...
8}
Kód vygenerovaný oapi-codegen prostredníctvom vyššie uvedeného rozhrania vykonáva logiku analýzy a validácie parametrov dotazu, hlavičky a tela a volá príslušnú metódu deklarovanú v rozhraní. Používateľ musí implementovať len implementáciu vyššie uvedeného rozhrania a práca potrebná na implementáciu API je dokončená.
Výhody:
- Keďže sa špecifikácia objaví skôr, ako sa začne vývoj, je výhodné paralelne vykonávať prácu v prípade spolupráce viacerých tímov.
- Kód pre časti, ktoré sa predtým pracne opakovali, sa generuje automaticky, čo zvyšuje efektivitu práce a zároveň je stále výhodné pre ladenie.
- Je ľahké zaručiť, že dokumentácia a tvar kódu sú vždy rovnaké.
Nevýhody:
- Ak nemáte žiadne znalosti o samotnej špecifikácii OpenAPI, počiatočná krivka učenia je mierne vyššia.
- Keďže tvar kódu na manipuláciu s API sa generuje automaticky projektom, môže byť náročné reagovať, ak je potrebné prispôsobenie.
Komentár autora. K 10/2024 generovaný kód Go generovaný nástrojom OpenAPI Generator vynucuje nielen logiku API, ale aj celkový tvar projektu, a keďže štruktúra projektu je rigidná, generuje kód, ktorý nie je vhodný na pridávanie rôznych funkcií potrebných pre skutočné produkčné prostredie. Pre tých, ktorí si vyberú tento prístup, dôrazne odporúčame používať oapi-codegen. Autor používa oapi-codege + echo + StrictServerInterface.
3. Generovanie dokumentácie špecifikácie OpenAPI pomocou kódu Go
Ak na jednom serveri pracuje desiatky alebo stovky ľudí, nevyhnutne dochádza k problémom, pri ktorých môže byť narušená konzistentnosť jednotlivých API. Intuitívnym príkladom je, že ak deklarujete špecifikáciu pre viac ako 100 koncových bodov API v jednom súbore OpenAPI yaml, tento súbor sa stane monštrom s viac ako 10 000 riadkami, a pri deklarovaní nového koncového bodu API sa nevyhnutne duplikuje ten istý model alebo sa vynechá niekoľko polí, alebo sa začne narúšať celková konzistentnosť API, ako napríklad vznik nevhodného pomenovania ciest.
Na vyriešenie týchto problémov môžete mať samostatného vlastníka, ktorý spravuje OpenAPI yaml, alebo vyvinúť Linter, ktorý automaticky zachytáva chyby počas procesu CI, ale pomocou jazyka Domain-specific language (DSL) v jazyku Go môžete vynútiť, aby všetky API mali konzistentnú jednotnosť.
Reprezentatívnym projektom, ktorý používa túto techniku, je Kubernetes (vytvorený samostatne bez ďalšej knižnice) a môžete ho použiť pomocou projektov ako go-restful, goa. Nasleduje príklad použitia goa
.
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})
Ak napíšete kompilovateľný kód Go, ako je uvedené vyššie, získate výhodu, že implementácia API POST /users
a definícia dokumentácie sú dokončené súčasne.
Výhody:
- Keďže všetko pochádza z kódu, je jednoduché zachovať konzistentnosť API v celom projekte.
- Využitím silného typového systému Go môžete získať presnejšiu a nespornejšiu špecifikáciu, ako keď používate všetky funkcie OpenAPI3.
Nevýhody:
- Musíte sa naučiť DSL definované jednotlivými frameworkami a môže byť ťažké ho použiť na existujúci kód.
- Keďže musíte povinne dodržiavať pravidlá navrhované rámcom, môže sa znížiť sloboda a flexibilita.
Na záver
Každá metóda má svoje výhody a nevýhody a je dôležité vybrať si vhodnú metódu podľa požiadaviek projektu a preferencií tímu. Najdôležitejšie je vždy posúdiť, ktoré riešenie je najvhodnejšie pre vašu súčasnú situáciu, a zvýšiť produktivitu vývoja, aby ste si mohli užívať skorý odchod z práce a uspokojivú rovnováhu medzi prácou a súkromným životom.
Hoci tento článok bol napísaný k 10/2024, ekosystémy Go a OpenAPI sa neustále vyvíjajú, preto by ste mali sledovať aktuálny stav jednotlivých knižníc a projektov a ich zmenené výhody a nevýhody, berúc do úvahy časový odstup, keď si tento článok prečítate.
Prajem vám šťastný Go život~ 😘