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

Въведение
Когато разработвате Production Backend сървър на Go, почти всеки разработчик рано или късно се сблъсква с един от първите сериозни проблеми:
Как да документирам API...?
След кратко търсене става ясно, че е полезно документацията да се пише според спецификацията OpenAPI, и естествено се намират библиотеки, които работят с OpenAPI. Но дори след това решение възниква следващият въпрос:
Има много библиотеки за OpenAPI.. Коя да използвам...?
Този документ е кратко въведение в библиотеките, написано за начинаещи в Go, които са в подобна ситуация. Документът е актуален към края на 2024 г., а екосистемата на езика се променя непрекъснато, затова препоръчваме да го използвате като отправна точка и да следите последните новини за проектите.
Стратегии на библиотеките за работа с OpenAPI
Вероятно вече знаете, че OpenAPI е спецификация за ясно дефиниране и документиране на REST API. Тя описва крайните точки на API, заявките и форматите на отговорите в YAML или JSON, което не само помага на разработчиците, но и автоматизира генерирането на код за фронтенд и бекенд, намалява безсмислените повторения и дребните човешки грешки.
За да се интегрира OpenAPI естествено в проекта, библиотеките в екосистемата на Go обикновено следват три основни стратегии.
1. Съставяне на OpenAPI спецификация от Go коментари
При работа с API според OpenAPI една от трудностите е, че реалният документ и кодът, който го реализира, се намират в отделни файлове и на различни места. Често се случва кодът да бъде променен, без документът да бъде обновен, или документът да бъде обновен, без кодът да бъде променен.
Ето един прост пример:
- В файла
./internal/server/user.goпроменяте логиката на API. - Реалният документ се намира в
./openapi3.yaml, а промяната в него може да бъде пропусната по невнимание. - Без да забележите проблема, изпращате Pull Request и го давате на колегите за преглед.
- Рецензентите също не виждат промените в
./openapi3.yaml, така че може да се случи неприятна ситуация: спецификацията на API остава същата, но реалната реализация на API е променена.
Когато API документацията се пише като Go коментари, този проблем се решава донякъде. Кодът и документацията са на едно място, затова при промяна на кода коментарите могат да се актуализират заедно с него. Съществуват инструменти, които автоматично генерират OpenAPI спецификация въз основа на тези коментари.
Един представителен проект е Swag. Swag анализира коментарите в Go кода и генерира документ във формат OpenAPI 2. Използването е просто: над функцията обработчик се пишат коментари в формата, определен от библиотеката.
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 репозиторий, в крайния билд или в отделна външна система за управление на API документация, за да се използва при сътрудничеството с други проекти.
Предимства:
- Тъй като коментарите са заедно с кода, вероятността реалният код и документацията да се разминат намалява.
- Документирането е лесно и гъвкаво, като е достатъчно само да се напишат коментари, без допълнителни инструменти или сложна конфигурация.
- Коментарите не влияят на реалната логика на API, затова е удобно да се добавят временни функции, чиято документация не е желателно да бъде публична.
Недостатъци:
- С увеличаване на броя на редовете коментари четимостта на отделния файл с код може да се влоши.
- Не винаги е възможно да се опишат всички API спецификации във формата на коментари.
- Тъй като документът не налага кода, не може да се гарантира, че OpenAPI документацията съвпада с реалната логика.
2. Генериране на Go код от OpenAPI спецификация
Съществува и подход, при който Single source of Truth (SSOT) се намира не в Go кода, а в документа. Първо се дефинира OpenAPI спецификацията, а после на нейна основа се генерира Go код. Тъй като спецификацията на API генерира кода, може да се наложи API да се проектира първо, което е сред предимствата на този подход: пропуснатите части не се откриват след приключване на разработката и цялостният код не се променя заедно със спецификацията, а този проблем се предотвратява рано.
Представителни проекти, които използват този подход, са oapi-codegen и OpenAPI Generator. Използването е просто:
- Пишете документ в YAML или JSON според спецификацията OpenAPI.
- Стартирате CLI инструмента.
- Генерира се съответният Go stub код.
- Оставате само да реализирате детайлната логика за отделните API, които stub кодът използва.
Следва пример за код, генериран от 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}
Генерираният от oapi-codegen код, базиран на този interface, извършва обработката на query параметри, header и body, валидацията и извиква подходящия метод, дефиниран в interface. Потребителят трябва само да реализира този interface, за да завърши работата по API.
Предимства:
- Тъй като спецификацията се създава първо и разработката следва нея, при работа на няколко екипа е удобно задачите да се изпълняват паралелно.
- Кодът за повтарящите се части се генерира автоматично, което повишава ефективността, като същевременно дебъгването остава лесно.
- Лесно е да се гарантира, че документацията и формата на кода винаги съвпадат.
Недостатъци:
- При липса на познания за спецификацията OpenAPI началната крива на обучение е доста стръмна.
- Тъй като кодът за обработка на API се генерира автоматично според проекта, при нужда от персонализация може да е трудно да се справите.
Коментар на автора. Към октомври 2024 г. Go кодът, генериран от OpenAPI Generator, налага не само логиката на API, но и цялата структура на проекта. Структурата е твърда, затова този код е неподходящ за добавяне на различните функции, необходими в Production среда. Ако избирате този подход, силно препоръчваме oapi-codegen. Авторът използва oapi-codegen + echo + StrictServerInterface.
3. Генериране на OpenAPI спецификация от Go код
Когато десетки или стотици души разработват един и същ сървър, неизбежно възниква проблем: единството между отделните API може да се наруши. Например, ако спецификацията на над 100 API крайни точки се съдържа в един OpenAPI YAML файл, той лесно ще надхвърли 10 000 реда. При добавянето на нова крайна точка се появяват дублирани модели, пропуснати полета или Path имена, които не съответстват на конвенцията, и цялостното единство на API започва да се разпада.
За решаване на този проблем може да се назначи отделен Owner за OpenAPI YAML файла или да се разработи Linter, който автоматично да открива грешките по време на CI процеса. Но с Go можете да дефинирате Domain-specific language (DSL) и така да наложите единен стил за всички 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 код по този начин, получавате едновременно реализацията на API POST /users и дефиницията на документацията.
Предимства:
- Тъй като всичко се генерира от кода, лесно се поддържа единство на API в целия проект.
- Благодарение на строгата типова система на Go може да се получи по-точна спецификация, в която има по-малко спорни моменти, отколкото при използване на всички възможности на OpenAPI 3.
Недостатъци:
- Трябва да научите DSL, дефиниран от всяка рамка, и може да е трудно да се приложи към съществуващ код.
- Тъй като трябва да следвате правилата, предложени от рамката, свободата и гъвкавостта може да са ограничени.
Заключение
Всеки подход има своите предимства и недостатъци, затова е важно да изберете подходящия според изискванията на проекта и предпочитанията на екипа. Най-важното не е кой метод е по-добър, а да прецените какво решение е най-подходящо за текущата ви ситуация и да повишите производителността си, за да можете да си тръгвате рано и да се радвате на добър баланс между работата и личния живот.
Тъй като статията е написана към октомври 2024 г., а екосистемата на Go и OpenAPI непрекъснато се развива, препоръчваме да следите актуалното състояние на библиотеките и проектите и промените в техните предимства и недостатъци, като вземете предвид времето, в което четете този текст.
Чудесен Go живот ви пожелавам~ 😘