GoSuda

Go a ekosystém OpenAPI

By iwanhae views ...

Úvod

Pri vývoji produkčného backendového servera v jazyku Go sa takmer každý vývojár pravdepodobne ako prvé stretne s jednou z nasledujúcich výziev.

Ako mám dokumentovať API...?

Po krátkom hľadaní sa ukáže, že je výhodné zapísať dokumentáciu v súlade so špecifikáciou OpenAPI, a prirodzene sa začne hľadať knižnica, ktorá je s OpenAPI prepojená. Aj po takomto rozhodnutí však nasleduje ďalší problém.

Existuje veľa knižníc súvisiacich s OpenAPI.. Akú mám použiť...?

Tento dokument je stručným úvodom do knižníc, ktorý som napísal pre začiatočníkov v jazyku Go, ktorí sa stretávajú s touto situáciou. Dokument vznikol k stavu z konca roka 2024, a keďže ekosystém jazyka sa neustále mení, odporúčam ho používať ako referenciu a zároveň sledovať aktuálne novinky.

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. Umožňuje definovať endpointy API, formáty požiadaviek a odpovedí a podobne vo formáte YAML alebo JSON. Pomáha tak nielen vývojárom, ale aj generovaniu kódu na strane frontendu a backendu, čím sa zautomatizuje zbytočné opakovanie a znižuje sa počet drobných ľudských chýb.

Na prirodzené prepojenie OpenAPI s projektom sa knižnice v ekosystéme Go zvyčajne riadia tromi nasledujúcimi stratégiami.

1. Zostavenie OpenAPI špecifikácie z Go komentárov

Náročným aspektom pri vývoji API v súlade s OpenAPI je skutočnosť, že skutočná dokumentácia a kód, ktorý ju implementuje, sa nachádzajú v samostatných súboroch na úplne odlišných miestach. Preto nie je nezvyčajné, že sa kód aktualizuje bez úpravy dokumentácie, alebo naopak, dokumentácia sa aktualizuje, no kód nie.

Jednoduchý príklad:

  1. Upravíte logiku API v súbore ./internal/server/user.go.
  2. Skutočná dokumentácia sa nachádza v ./openapi3.yaml a na jej zmenu môžete omylom zabudnúť.
  3. Ak si túto nezrovnalosť neuvedomíte, odošlete Pull Request a požiadate kolegov o review.
  4. Keďže ani recenzenti nevidia zmeny v ./openapi3.yaml, môže dôjsť k nežiaducemu stavu, v ktorom sa špecifikácia API nezmení, ale skutočná implementácia API áno.

Ak API dokumentáciu zapíšete ako Go komentáre, tento problém sa do istej miery eliminuje. Kód a dokumentácia sú sústredené na jednom mieste, takže pri úprave kódu môžete komentáre aktualizovať súčasne. Existujú nástroje, ktoré na základe týchto komentárov automaticky generujú OpenAPI špecifikáciu.

Reprezentatívnym projektom je Swag. Swag parsuje komentáre v Go kóde a generuje dokumentáciu vo formáte OpenAPI 2. Používanie je jednoduché: nad handler funkciu stačí napísať komentáre v tvare, ktorý definuje konkrétna knižnica.

 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}

Po napísaní takýchto komentárov CLI nástroj Swag tieto komentáre spracuje a vygeneruje dokumentáciu OpenAPI 2. Tento proces sa zvyčajne vykonáva v rámci CI procesu a vygenerovaná špecifikácia OpenAPI sa nasadzuje do Git repozitára, do finálnych buildov alebo do samostatného externého systému na správu API dokumentácie, odkiaľ ju využívajú ostatné projekty pri spolupráci.

Výhody:

  • Keďže komentáre sa nachádzajú spolu s kódom, pravdepodobnosť, že sa skutočný kód a dokumentácia rozchádzajú, je nižšia.
  • Dokumentovanie je jednoduché a flexibilné, pretože stačí len komentáre, bez dodatočných nástrojov či zložitej konfigurácie.
  • Komentáre neovplyvňujú skutočnú logiku API, preto sa dobre hodia na pridávanie dočasných funkcií, ktorých zverejnenie v dokumentácii je nežiaduce.

Nevýhody:

  • S rastúcim počtom riadkov komentárov môže čitateľnosť jedného zdrojového súboru klesnúť.
  • Vyjadriť všetky špecifikácie API pomocou komentárov môže byť náročné.
  • Dokumentácia nevynucuje kód, preto nemožno zaručiť, že dokumentácia OpenAPI a skutočná logika sú konzistentné.

2. Generovanie Go kódu zo špecifikácie OpenAPI

Existuje aj prístup, pri ktorom je Single Source of Truth (SSOT) umiestnený v dokumentácii, nie v Go kóde. Ide o to, že sa najprv definuje špecifikácia OpenAPI a na jej základe sa generuje Go kód. Keďže špecifikácia API generuje kód, dá sa vďaka tomu vynútiť návrh API ešte pred implementáciou. Keďže definícia špecifikácie API je prvým krokom vo vývojovom procese, tento prístup má výhodu v tom, že nedostatky sa odhalia včas a predchádza sa situáciám, keď sa až po dokončení vývoja zistí chýbajúca časť a celý kód sa musí upravovať spolu so zmenou špecifikácie API.

Reprezentatívnymi projektmi, ktoré tento prístup využívajú, sú oapi-codegen a OpenAPI Generator. Použitie je jednoduché:

  1. Napíšete dokument OpenAPI vo formáte YAML alebo JSON v súlade so špecifikáciou.
  2. Spustíte CLI.
  3. Vygeneruje sa zodpovedajúci Go stub kód.
  4. Tento stub stačí doplniť o implementáciu detailnej logiky jednotlivých API.

Nasleduje príklad kódu, ktorý generuje oapi-codegen.

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}

Kód vygenerovaný nástrojom oapi-codegen na základe tohto interface zabezpečuje spracovanie query parametrov, hlavičiek a tela požiadavky, validáciu a podobne, a následne volá vhodnú metódu deklarovanú v interface. Používateľ potrebuje implementovať iba konkrétnu implementáciu tohto interface, a tým je práca na implementácii API hotová.

Výhody:

  • Keďže špecifikácia vzniká ako prvá a vývoj prebieha až potom, je výhodné pracovať na úlohách paralelne v prípade spolupráce viacerých tímov.
  • Kód pre opakujúcu sa manuálnu prácu sa generuje automaticky, čo zvyšuje produktivitu a zároveň ostáva výhodné pri ladení.
  • Zabezpečiť, aby sa dokumentácia a kód vždy zhodovali, je jednoduchšie.

Nevýhody:

  • Ak nepoznáte špecifikáciu OpenAPI, úvodná krivka učenia môže byť mierne strmá.
  • Keďže štruktúra kódu, ktorý spracováva API, sa generuje automaticky podľa projektu, v prípade potreby prispôsobenia môže byť náročné na ňu reagovať.

Komentár autora. K októbru 2024 kód generovaný nástrojom OpenAPI Generator vynucuje nielen logiku API, ale aj celú štruktúru projektu. Projekt je tak zviazaný a generuje kód, ktorý sa nehodí na pridávanie rôznych funkcií potrebných v produkčnom prostredí. Tých, ktorí sa rozhodnú pre tento prístup, dôrazne odporúčam použiť oapi-codegen. Autor používa kombináciu oapi-codegen + echo + StrictServerInterface.

3. Generovanie dokumentácie OpenAPI z Go kódu

Keď na jednom serveri pracuje desiatky alebo stovky ľudí, nevyhnutne sa objaví problém, že jednotnosť jednotlivých API sa môže narušiť. Ako názorný príklad: ak by sa špecifikácia viac ako 100 API endpointov deklarovala v jedinom súbore OpenAPI YAML, tento súbor by sa stal obrím útvarom s viac ako 10 000 riadkami. Pri deklarovaní nového API endpointu by sa nevyhnutne duplikovali rovnaké modely, niektoré polia by sa vynechali alebo by vznikali názvy ciest, ktoré nezodpovedajú konvenciám. Celková jednotnosť API by sa tak začala strácať.

Na riešenie týchto problémov by bolo možné určiť vlastníka, ktorý bude spravovať YAML súbor OpenAPI, alebo vyvinúť linter, ktorý tieto problémy automaticky odhalí v CI procese. Jazyk Go však umožňuje definovať Domain-specific language (DSL), vďaka ktorému je možné vynútiť konzistentnosť všetkých API.

Reprezentatívnym projektom, ktorý používa túto techniku, je Kubernetes (implementuje ju vlastnými prostriedkami bez použitia externej knižnice). Môžete tiež použiť projekty ako go-restful alebo 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ý Go kód tak, ako je uvedené vyššie, získate výhodu v tom, že implementácia API POST /users aj definícia jeho dokumentácie sú hotové súčasne.

Výhody:

  • Keďže všetko vzniká z kódu, je jednoduché udržať konzistentnosť API v celom projekte.
  • Využitím silného typového systému jazyka Go je možné dosiahnuť presnejšiu špecifikáciu bez sporov než pri použití všetkých funkcií OpenAPI 3.

Nevýhody:

  • Je potrebné naučiť sa DSL definované v jednotlivých frameworkoch, a aplikovanie na existujúci kód môže byť náročné.
  • Keďže sa musia dodržiavať pravidlá navrhnuté frameworkom, voľnosť a flexibilita môžu byť obmedzené.

Záver

Každý prístup má svoje výhody a nevýhody a dôležité je zvoliť vhodný prístup podľa požiadaviek projektu a preferencií tímu. Najdôležitejšie však nie je to, ktorý spôsob je lepší, ale to, aby ste zhodnotili, aké riešenie je najvhodnejšie pre vašu aktuálnu situáciu, a zvyšovali produktivitu vývoja, vďaka čomu si budete môcť užívať skoré odchody z práce a spokojnú work-life balance.

Hoci som tento článok napísal k októbru 2024, ekosystémy jazyka Go a OpenAPI sa neustále vyvíjajú. Preto vám odporúčam sledovať aktuálne novinky a zmenené výhody a nevýhody jednotlivých knižníc a projektov, pričom zohľadnite čas, ktorý uplynul od napísania tohto článku.

Prajem vám šťastný život s Go 😘