Go и екосистемата OpenAPI
Въведение
При разработването на 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 е, че действителният документ и кодът, който го имплементира, съществуват като отделни файлове на съвсем различни места, което води до ситуации, в които кодът е актуализиран, но документът не е, или документът е актуализиран, но кодът не е, което се случва по-често, отколкото се очаква.
Прост пример:
- Логиката за API е променена във файла
./internal/server/user.go
. - Действителният документ съществува в
./openapi3.yaml
и промяната е пропусната по погрешка. - Ако проблемът с тази промяна не бъде забелязан и бъде подаден Pull Request за преглед от колеги.
- Тъй като промените в
./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. Използването е лесно.
- Създайте yaml или json документ в съответствие със спецификацията OpenAPI.
- Изпълнете CLI.
- Генерира се Go stub код, който съответства на него.
- Сега трябва да приложите директно само подробната логика за всеки 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 живот~ 😘