GoSuda

Go и екосистемата OpenAPI

By iwanhae
views ...

Въведение

При разработването на Production Backend сървър с Go, почти всички разработчици се сблъскват с едно от първите предизвикателства:

Как да документираме API...?

След кратко проучване става ясно, че е полезно да се създаде документация, съответстваща на спецификацията OpenAPI, и естествено се търси библиотека, която да се интегрира с OpenAPI. Но дори и след вземането на това решение, остава следният проблем:

Има много библиотеки, свързани с OpenAPI... Коя да използвам...?

Този документ е кратко въведение в библиотеки, написано за Go начинаещи, които се намират в тази ситуация. Документът е написан към края на 2024 г. и тъй като екосистемата на езика винаги се променя динамично, препоръчително е да се консултирате с най-новите тенденции, докато го използвате.

Стратегии на библиотеките за работа с OpenAPI

Както може би вече знаете, OpenAPI е спецификация за ясно дефиниране и документиране на REST API. Тя дефинира крайните точки на API, заявките, форматите на отговори и т.н. във формат YAML или JSON, автоматизирайки генерирането на код не само за разработчици, но и за front-end и back-end, намалявайки безсмислените повторения и спомагайки за намаляване на незначителни човешки грешки.

За да се интегрират естествено тези OpenAPI в проектите, библиотеките в Go екосистемата възприемат три основни стратегии:

1. Комбиниране на Go коментари в OpenAPI спецификационни документи

Един от трудните моменти при разработването на API в съответствие с OpenAPI е, че действителният документ и кодът, който го имплементира, съществуват като отделни файлове на съвсем различни места, което води до ситуации, в които кодът е актуализиран, но документът не е, или документът е актуализиран, но кодът не е, което се случва по-често, отколкото се очаква.

Прост пример:

  1. Логиката за API е променена във файла ./internal/server/user.go.
  2. Действителният документ съществува в ./openapi3.yaml и промяната е пропусната по погрешка.
  3. Ако проблемът с тази промяна не бъде забелязан и бъде подаден Pull Request за преглед от колеги.
  4. Тъй като промените в ./openapi3.yaml не са видими за рецензентите. Може да възникне нещастен инцидент, при който API спецификацията остава същата, но действителната API имплементация е променена.

Писането на API документация във формата на Go коментари може да разреши този проблем до известна степен. Тъй като кодът и документът са на едно място, коментарите могат да бъдат актуализирани едновременно с модифицирането на кода. Съществуват инструменти, които автоматично генерират OpenAPI спецификационни документи въз основа на тези коментари.

Представителен проект е Swag. Swag анализира коментарите в Go кода и генерира документи във формат OpenAPI 2. Използването е лесно. Просто напишете коментари върху функцията handler във формата, определена от всяка библиотека.

 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}

След като напишете коментарите по този начин, CLI Swag анализира тези коментари и генерира OpenAPI 2 документ. Обикновено тази задача се извършва в процеса на CI, а генерираният OpenAPI спецификационен документ се разпространява в Git Repository, крайните резултати от компилацията или отделна външна система за управление на API документация и се използва за сътрудничество с други проекти.

Предимства:

  • Тъй като коментарите са заедно с кода, вероятността действителният код и формата на документа да се различават е намалена.
  • Можете лесно и свободно да документирате само с коментари, без отделни инструменти или сложни настройки.
  • Тъй като коментарите не влияят на действителната API логика, е добре да добавите временни функции, които са неудобни за публикуване като документация.

Недостатъци:

  • Тъй като броят на редовете в коментарите се увеличава, четливостта на единичен кодов файл може да намалее.
  • Може да е трудно да се изрази цялата API спецификация във формата на коментари.
  • Тъй като документът не принуждава кода, не може да се гарантира, че документацията на OpenAPI и действителната логика съвпадат.

2. Генериране на Go код от OpenAPI спецификационен документ

Съществува и метод за поставяне на Single source of Truth (SSOT) не в Go кода, а в документа. Това е метод за първо дефиниране на OpenAPI спецификацията и генериране на Go код въз основа на дефинираното съдържание. Тъй като API спецификацията генерира код, тя може да наложи API проектирането първо културно и тъй като дефинирането на API спецификацията е първата стъпка в последователността на разработка, тя има силата да предотврати възникването на нещастни инциденти, като например осъзнаване на пропуснати части след завършване на разработката и модифициране на целия код заедно с промените в API спецификацията.

Представителни проекти, които възприемат този метод, са oapi-codegen и OpenAPI Generator. Използването е лесно.

  1. Създайте yaml или json документ в съответствие със спецификацията OpenAPI.
  2. Изпълнете CLI.
  3. Генерира се Go stub код, който съответства на него.
  4. Сега трябва да приложите директно само подробната логика за всеки API, така че този stub да може да го използва.

Следва пример за код, генериран от oapi-codegen.

1// StrictServerInterface представлява всички handler-и на сървъра.
2type StrictServerInterface interface {
3	// ...
4	// Връща всички домашни любимци
5	// (GET /pets)
6	FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
7	// ...
8}

Чрез този интерфейс кодът, генериран от oapi-codegen, изпълнява логика като query parameters, header, body parsing и Validation и извиква подходящ метод, деклариран в интерфейса. Потребителят трябва само да имплементира имплементация за този интерфейс, за да завърши работата, необходима за имплементиране на API.

Предимства:

  • Тъй като спецификацията излиза първа и разработката продължава, е изгодно да се извършва работата паралелно, когато няколко екипа си сътрудничат.
  • Кодът за частите, които са били работени като повтаряща се тежка работа, се генерира автоматично, така че ефективността на работата се увеличава, но все пак е изгодно за отстраняване на грешки.
  • Лесно е да се гарантира, че формата на документа и кода винаги съвпада.

Недостатъци:

  • Ако сте невежи за самата спецификация OpenAPI, има известна начална крива на обучение.
  • Тъй като формата на кода, който обработва API, се генерира автоматично от проекта, може да е трудно да се реагира, ако е необходимо персонализиране.

Коментар на автора. Към октомври 2024 г. Go кодът, генериран от OpenAPI Generator, налага не само API логиката, но и цялата форма на проекта, а структурата на проекта е твърда, така че той генерира код във форма, неподходяща за добавяне на различни функции, необходими за действителната Production среда. Тези, които възприемат този метод, силно се препоръчва да използват oapi-codegen. Авторът използва oapi-codegen + echo + StrictServerInterface.

3. Генериране на OpenAPI спецификационни документи с Go код

Проблем, който неизбежно възниква, когато десетки или стотици хора разработват един и същ сървър, е, че последователността може да бъде нарушена за всеки отделен API. Като интуитивен пример, ако спецификациите за повече от 100 API Endpoint-а са декларирани в един OpenAPI yaml файл, този файл ще се превърне в чудовище, надхвърлящо 10 000 реда, и неизбежно ще дублирате същия модел, докато декларирате нов API Endpoint, или ще пропуснете няколко полета, или ще се появи Path naming, което не съответства на конвенцията, и ще започне да се нарушава цялостната последователност на API.

За да разрешите този проблем, можете да имате отделен Owner за управление на OpenAPI yaml или можете да разработите Linter и да предприемете стъпки за автоматично улавяне по време на процеса на CI, но можете да дефинирате Domain-specific language (DSL) с Go език, за да принудите всички API да имат последователна консистентност.

Kubernetes е представителен проект, който използва тази техника (създаден независимо без отделна библиотека), и можете също да опитате да го използвате с проекти като go-restful, goa. Следва пример за използване на 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})

Като напишете компилируем Go код, както е показано по-горе, можете да получите силата да завършите едновременно имплементацията и дефиницията на документа за POST /users API.

Предимства:

  • Тъй като всичко идва от кода, е лесно да се поддържа API консистентност за целия проект.
  • Можете да получите по-точна и безспорна спецификация, отколкото когато използвате всички функции на OpenAPI3, като използвате системата за силно типизиране на Go.

Недостатъци:

  • Трябва да научите DSL, дефиниран във всяка рамка, и може да е трудно да се приложи към съществуващ код.
  • Тъй като трябва да следвате насилствено правилата, предложени от рамката, свободата и гъвкавостта могат да бъдат намалени.

В заключение

Всеки метод има своите предимства и недостатъци и е важно да изберете подходящия метод в зависимост от изискванията на проекта и предпочитанията на екипа. Най-важното е не кой метод е най-добрият, а да се извърши оценка на стойността за това кое е най-подходящото решение за текущата ситуация и да се увеличи производителността на разработка, за да се постигне бързо прибиране от работа и да се насладите на задоволителен баланс между работа и личен живот.

Въпреки че пиша тази статия към октомври 2024 г., Go и OpenAPI екосистемите непрекъснато се развиват, така че, моля, вземете предвид интервала между момента, в който четете тази статия, и продължете да следите последните тенденции на всяка библиотека и проект, както и техните променени предимства и недостатъци.

Приятен Go живот~ 😘