GoSuda

Go a ekosystém OpenAPI

By iwanhae
views ...

Úvod

Při vývoji produkčního backend serveru v jazyce Go se téměř všichni vývojáři setkají na samém začátku s jednou z následujících obtíží:

Jak dokumentovat API...?

Po krátkém průzkumu si uvědomíte, že je výhodné vytvářet dokumentaci podle specifikace OpenAPI, a přirozeně tak začnete hledat knihovnu, která s OpenAPI spolupracuje. Nicméně i po tomto rozhodnutí nastává další problém:

Existuje mnoho knihoven souvisejících s OpenAPI... kterou z nich si vybrat...?

Tento dokument je stručný úvodní text o knihovnách, který je určen pro začátečníky v Go, kteří se ocitli v této situaci. Byl napsán na konci roku 2024 a vzhledem k tomu, že se jazykový ekosystém neustále dynamicky mění, doporučujeme si jej prohlédnout a zároveň sledovat nejnovější vývoj.

Strategie knihoven pro práci s OpenAPI

Jak už možná víte, OpenAPI je specifikace pro jasné definování a dokumentaci REST API. Definuje koncové body API, formáty požadavků a odpovědí ve formátu YAML nebo JSON, což pomáhá nejen vývojářům, ale také automatizuje generování kódu pro frontend i backend, čímž se snižuje zbytečné opakování a významně se omezují drobné lidské chyby.

Pro přirozené propojení OpenAPI s projektem se knihovny v ekosystému Go řídí v zásadě těmito třemi strategiemi:

1. Sestavení dokumentace specifikace OpenAPI z komentářů v Go

Jednou z obtíží při vývoji API podle OpenAPI je fakt, že skutečná dokumentace a kód, který ji implementuje, se nacházejí v samostatných souborech na zcela odlišných místech. Proto se poměrně často stává, že se kód aktualizuje, ale dokumentace nikoli, nebo se dokumentace aktualizuje, ale kód nikoli.

Pro jednoduchost si uveďme příklad:

  1. Ve souboru ./internal/server/user.go upravíte logiku API.
  2. Skutečná dokumentace se nachází v souboru ./openapi3.yaml a na tuto změnu se omylem zapomene.
  3. Pokud si nevšimnete problému s těmito změnami a odešlete Pull Request k recenzi kolegům.
  4. Recenzenti si také nevšimnou změn v souboru ./openapi3.yaml. Může dojít k nešťastné situaci, kdy zůstane specifikace API beze změny, ale skutečná implementace API se změní.

Zápisem dokumentace API ve formě komentářů v Go lze tento problém do jisté míry vyřešit. Kód a dokumentace jsou na jednom místě, takže je možné aktualizovat komentáře spolu s úpravami kódu. Existují nástroje, které automaticky generují dokumentaci specifikace OpenAPI na základě těchto komentářů.

Reprezentativním projektem je Swag. Swag parsuje komentáře v kódu Go a generuje dokumentaci ve formátu OpenAPI 2. Použití je jednoduché. Stačí napsat komentáře ve formátu definovaném pro každou knihovnu nad funkcemi obsluhy.

 1// @Summary Vytvoření uživatele
 2// @Description Vytvoří nového uživatele.
 3// @Tags Users
 4// @Accept json
 5// @Produce json
 6// @Param user body models.User true "Informace o uživateli"
 7// @Success 200 {object} models.User
 8// @Failure 400 {object} models.ErrorResponse
 9// @Router /users [post]
10func CreateUser(c *gin.Context) {
11    // ...
12}

Pokud jsou komentáře napsány tímto způsobem, CLI Swag tyto komentáře parsuje a generuje dokumentaci OpenAPI 2. Obecně se tato operace provádí v procesu CI a vygenerovaná dokumentace specifikace OpenAPI se nasazuje do repozitáře Git, konečných sestavení nebo do samostatného externího systému pro správu dokumentace API a používá se při spolupráci s jinými projekty.

Výhody:

  • Protože jsou komentáře spolu s kódem, snižuje se pravděpodobnost, že se skutečný kód a dokumentace budou lišit.
  • Dokumentaci lze jednoduše a volně provádět pouze pomocí komentářů bez nutnosti použití samostatných nástrojů nebo složitého nastavení.
  • Komentáře neovlivňují skutečnou logiku API, takže je výhodné přidávat dočasné funkce, které je obtížné zveřejnit jako dokumentaci.

Nevýhody:

  • S narůstajícím počtem řádků komentářů se může snížit čitelnost jednoho souboru kódu.
  • Je obtížné vyjádřit všechny specifikace API ve formě komentářů.
  • Dokumentace nevynucuje kód, takže nelze zaručit, že dokumentace OpenAPI bude odpovídat skutečné logice.

2. Generování kódu Go z dokumentace specifikace OpenAPI

Existuje také metoda, která považuje dokumentaci, nikoli kód Go, za jediný zdroj pravdy (SSOT). Jde o přístup, kdy se nejprve definuje specifikace OpenAPI a na základě definovaného obsahu se generuje kód Go. Vzhledem k tomu, že specifikace API generuje kód, lze kulturně vynutit, aby se návrh API prováděl jako první, a protože je definování specifikace API prvním krokem ve sledu vývoje, je možné v rané fázi předejít nehodám, kdy se po dokončení vývoje objeví přehlédnuté části a celý kód se upraví spolu se změnou specifikace API.

Reprezentativními projekty, které tento přístup používají, jsou oapi-codegen a OpenAPI Generator. Použití je jednoduché.

  1. Vytvoříte dokument ve formátu yaml nebo json podle specifikace OpenAPI.
  2. Spustíte CLI.
  3. Vygeneruje se odpovídající kód stub Go.
  4. Nyní stačí implementovat pouze podrobnou logiku pro jednotlivé API, aby bylo možné stub použít.

Následuje příklad kódu generovaného nástrojem 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ástrojem oapi-codegen prostřednictvím výše uvedeného rozhraní provádí logiku, jako je parsování parametrů dotazu, hlavičky, těla a ověřování, a volá příslušnou metodu deklarovanou v rozhraní. Uživatel musí implementovat pouze implementaci výše uvedeného rozhraní a je hotovo s prací potřebnou k implementaci API.

Výhody:

  • Vzhledem k tomu, že nejprve vznikne specifikace a teprve poté probíhá vývoj, je výhodné paralelně provádět práci v případě, že spolupracuje více týmů.
  • Protože se kód pro části prováděné rutinní a opakující se prací generuje automaticky, zvyšuje se efektivita práce a zároveň zůstává výhodný pro ladění.
  • Je snadné zaručit, že dokumentace a kód budou vždy stejné.

Nevýhody:

  • Pokud nejste obeznámeni se samotnou specifikací OpenAPI, existuje určitá počáteční křivka učení.
  • Vzhledem k tomu, že forma kódu pro zpracování API se generuje automaticky projektem, může být obtížné reagovat v případě potřeby přizpůsobení.

Poznámka autora. K říjnu 2024 generuje OpenAPI Generator kód Go, který vynucuje nejen logiku API, ale i celou formu projektu, a struktura projektu je rigidní, proto generuje kód, který není vhodný pro přidávání různých funkcí potřebných pro skutečné produkční prostředí. Těm, kteří si zvolí tento přístup, důrazně doporučuji používat oapi-codegen. Autor používá oapi-codege + echo + StrictServerInterface.

3. Generování dokumentace specifikace OpenAPI z kódu Go

Pokud na stejném serveru pracuje několik desítek nebo stovek lidí, je nevyhnutelné, že se naruší jednotnost jednotlivých API. Pro názornost, pokud se specifikace pro více než 100 koncových bodů API deklarují v jediném souboru OpenAPI yaml, stane se z tohoto souboru monstrum s více než 10 000 řádky a při deklarování nového koncového bodu API se nevyhnutelně začne narušovat celková jednotnost API, například opakovaným deklarováním stejného modelu, vynecháváním některých polí nebo vytvářením názvů cest, které neodpovídají konvencím.

Pro vyřešení tohoto problému lze sice samostatně jmenovat vlastníka pro správu souboru OpenAPI yaml nebo vyvinout Linter pro automatické zachycování během procesu CI, nicméně je možné definovat doménově specifický jazyk (DSL) v jazyce Go a vynutit tak, aby všechna API měla jednotnou konzistenci.

Reprezentativním projektem, který tuto techniku využívá, je Kubernetes (vytvořený samostatně bez knihovny), a můžete jej použít pomocí projektů, jako je go-restful a goa. Následuje příklad použití 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})

Pokud napíšete kompilovatelný kód Go, jak je uvedeno výše, můžete získat výhodu, že implementace a definice dokumentace pro API POST /users jsou dokončeny současně.

Výhody:

  • Vzhledem k tomu, že vše vychází z kódu, je snadné zachovat jednotnost API pro celý projekt.
  • Využitím silného typového systému jazyka Go lze získat přesnější a neproblematickou specifikaci než při využití všech funkcí OpenAPI3.

Nevýhody:

  • Je nutné naučit se DSL definované v každém frameworku a může být obtížné aplikovat je na stávající kód.
  • Vzhledem k tomu, že musíte striktně dodržovat pravidla navrhovaná frameworkem, může dojít ke snížení volnosti a flexibility.

Závěrem

Každá metoda má své výhody a nevýhody a je důležité zvolit vhodnou metodu v závislosti na požadavcích projektu a preferencích týmu. Nejdůležitější vždy není, jakou metodu je nejlepší použít, ale jaké je nejvhodnější řešení pro vaši aktuální situaci, a zvýšit produktivitu vývoje, abyste si mohli užívat rychlý odchod z práce a uspokojivou rovnováhu mezi pracovním a soukromým životem.

Ačkoli tento článek píši k říjnu 2024, ekosystém Go a OpenAPI se neustále vyvíjí, proto si prosím s ohledem na časový odstup mezi čtením tohoto článku průběžně sledujte novinky o jednotlivých knihovnách a projektech a jejich změněné výhody a nevýhody.

Přeji vám šťastný život s Go~ 😘