GoSuda

Go a ekosystém OpenAPI

By iwanhae
views ...

Úvod

Při vývoji Production Backend serveru v jazyce Go je jedním z prvních a nejčastějších problémů, se kterými se většina vývojářů setká, následující:

Jak dokumentovat API...?

Při bližším prozkoumání se zjistí, že je výhodné vytvářet dokumentaci odpovídající specifikaci OpenAPI, a přirozeně se začnou hledat knihovny, které se s OpenAPI integrují. Nicméně i po takovém rozhodnutí vyvstává další problém.

Existuje mnoho knihoven souvisejících s OpenAPI... Kterou bych měl použít...?

Tento dokument je stručným úvodem do knihoven, určeným pro začátečníky v Go, kteří se potýkají s touto situací. Dokument byl napsán na konci roku 2024 a ekosystém jazyka se neustále dynamicky mění, proto se doporučuje ho brát jako referenci a vždy sledovat nejnovější vývoj.

Strategie knihoven pro práci s OpenAPI

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

Pro přirozenou integraci OpenAPI do projektů využívají knihovny v ekosystému Go převážně následující tři strategie.

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

Jedním z úskalí při vývoji API v souladu s OpenAPI je to, že skutečná dokumentace a kód implementující tuto dokumentaci existují v oddělených souborech na zcela odlišných místech. To vede k častým situacím, kdy je aktualizován kód, ale dokumentace nikoli, nebo je aktualizována dokumentace, ale kód nikoli.

Jako jednoduchý příklad uveďme:

  1. Změníte logiku API v souboru ./internal/server/user.go.
  2. Skutečná dokumentace se nachází v ./openapi3.yaml, a tuto změnu můžete omylem zapomenout provést.
  3. Pokud odešlete Pull Request, aniž byste si uvědomili tento problém se změnami, a získáte recenzi od kolegů,
  4. Recenzenti také nevidí změny v ./openapi3.yaml. Může tak dojít k nešťastné situaci, kdy specifikace API zůstane stejná, ale skutečná implementace API se změní.

Tento problém lze do jisté míry vyřešit psaním dokumentace API ve formě komentářů v Go. Jelikož jsou kód a dokumentace na jednom místě, můžete aktualizovat komentáře společně s úpravou 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 nad funkcemi handleru v souladu s formátem definovaným každou knihovnou.

 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}

Když jsou komentáře napsány tímto způsobem, CLI nástroj Swag je parsuje a generuje dokumentaci OpenAPI 2. Tato operace se obvykle provádí v rámci CI procesu a vygenerovaná dokumentace specifikace OpenAPI je poté nasazena do Git Repository, konečného sestavení nebo do samostatného externího systému pro správu dokumentace API, kde je využívána pro spolupráci s jinými projekty.

Výhody:

  • Vzhledem k tomu, že komentáře jsou společně s kódem, snižuje se pravděpodobnost rozdílnosti mezi skutečným kódem a dokumentací.
  • Dokumentaci lze jednoduše a svobodně vytvářet pouze pomocí komentářů, bez nutnosti samostatných nástrojů nebo složité konfigurace.
  • Jelikož komentáře neovlivňují skutečnou logiku API, je to vhodné pro přidávání dočasných funkcí, které by bylo obtížné zveřejnit v dokumentaci.

Nevýhody:

  • S narůstajícím počtem řádků komentářů se může snížit čitelnost jednoho souboru kódu.
  • Vyjadřování všech specifikací API ve formě komentářů může být obtížné.
  • Jelikož dokumentace kód nevynucuje, nelze zaručit, že se dokument OpenAPI a skutečná logika shodují.

2. Generování kódu Go ze specifikace dokumentu OpenAPI

Existuje také přístup, kdy se Single Source of Truth (SSOT) nenachází v kódu Go, nýbrž v dokumentaci. Jedná se o metodu, kdy se nejprve definuje specifikace OpenAPI a na jejím základě se generuje kód Go. Jelikož specifikace API přímo generuje kód, lze ve vývojové kultuře vynutit nejprve návrh API. V posloupnosti vývoje je definování specifikace API prvním krokem, což má tu výhodu, že se předchází nešťastným událostem, kdy se po dokončení vývoje zjistí opomenuté části a je nutné upravit celou kódovou základnu společně se změnou specifikace API.

Mezi reprezentativní projekty, které tuto metodu přijímají, patří oapi-codegen a OpenAPI Generator. Použití je jednoduché.

  1. Napište YAML nebo JSON dokumentaci v souladu se specifikací OpenAPI.
  2. Spusťte CLI.
  3. Vygeneruje se odpovídající kód Go.
  4. Nyní stačí implementovat pouze detailní logiku pro jednotlivá API, aby mohl být tento stub použit.

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 generovaný nástrojem oapi-codegen na základě výše uvedeného rozhraní provádí logiku, jako je parsování query parametrů, hlaviček, těla a validace, a volá vhodnou metodu deklarovanou v rozhraní. Uživatelé stačí implementovat pouze implementaci pro výše uvedené rozhraní, čímž se dokončí práce potřebná pro implementaci API.

Výhody:

  • Vzhledem k tomu, že specifikace je vytvořena nejprve a poté probíhá vývoj, je to výhodné pro paralelní práci v případě, že spolupracuje více týmů.
  • Kód pro opakující se manuální práci se generuje automaticky, což zvyšuje efektivitu práce a zároveň je stále výhodné pro ladění.
  • Je snadné zajistit, že dokumentace a kód jsou vždy konzistentní.

Nevýhody:

  • Pokud jste v OpenAPI specifikaci nezkušení, počáteční křivka učení může být značná.
  • Jelikož se struktura kódu pro zpracování API generuje automaticky projektem, může být obtížné reagovat v případě potřeby přizpůsobení.

Komentář autora. K říjnu 2024 generovaný Go kód OpenAPI Generatoru vynucuje nejen logiku API, ale i celou strukturu projektu, což vede k rigidní struktuře projektu, která je nevhodná pro přidávání různých funkcí potřebných v produkčním prostředí. Těm, kteří zvolí tento přístup, důrazně doporučujeme použít oapi-codegen. Autor používá oapi-codege + echo + StrictServerInterface.

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

Když desítky nebo stovky lidí vyvíjejí stejný server, nevyhnutelně vzniká problém, že se konzistence jednotlivých API může narušit. Intuitivním příkladem je, že pokud deklarujete specifikaci pro více než 100 API koncových bodů v jediném souboru OpenAPI YAML, tento soubor se stane monstrem s více než 10 000 řádky. Při deklarování nových API koncových bodů se nevyhnutelně stane, že se duplicitně deklarují stejné modely, některé pole chybí, nebo vznikne pojmenování cest, které neodpovídá konvencím, což začne narušovat celkovou konzistenci API.

K řešení tohoto problému lze jmenovat vlastníka, který spravuje OpenAPI YAML, nebo vyvinout Linter, který automaticky zachytí chyby během CI procesu. Nicméně, pomocí jazyka Go lze definovat Domain-specific language (DSL) a vynutit, aby všechna API měla jednotnou konzistenci.

Reprezentativním projektem, který tuto techniku používá, je Kubernetes (který je vybudován interně bez samostatné knihovny). Lze také použít projekty jako 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})

Napsáním kompilovatelného kódu Go, jako je ten výše, získáte výhodu, že implementace API POST /users a definice dokumentace jsou dokončeny současně.

Výhody:

  • Jelikož vše vychází z kódu, je snadné udržet konzistenci API pro celý projekt.
  • Využitím silného typového systému Go lze získat přesnější a jednoznačnější specifikaci, než když se využijí všechny funkce OpenAPI3.

Nevýhody:

  • Je nutné naučit se DSL definovaný v každém frameworku a aplikace na existující kód může být obtížná.
  • Jelikož je nutné striktně dodržovat pravidla navržená frameworkem, může se snížit volnost a flexibilita.

Závěr

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ší není, jaký přístup je nejlepší, ale spíše provést hodnotové posouzení, které řešení je nejvhodnější pro vaši aktuální situaci, zvýšit produktivitu vývoje a užívat si rychlý odchod z práce a uspokojivou rovnováhu mezi pracovním a soukromým životem.

Ačkoli byl tento článek napsán v říjnu 2024, ekosystémy Go a OpenAPI se neustále vyvíjejí. Proto doporučujeme sledovat aktuální stav a změny v knihovnách a projektech s ohledem na časový odstup od přečtení tohoto článku.

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