---
id: e10df2108115b462a5ac2a289080a901
author: iwanhae
title: Go и экосистема OpenAPI
description: Представляем библиотеки и стратегии, которые можно использовать при разработке API на основе OpenAPI на языке Go.
language: ru
date: 2024-10-18T06:07:43.092628696Z
path: /blog/posts/go-and-the-openapi-ecosystem-zd5a8e472
category: api
---

### Введение

Когда разработчики начинают создавать производственный backend-сервер на Go, почти каждый сталкивается с одной из первых сложных задач:

> Как же документировать API...?

Немного поискав, вы понимаете, что выгодно писать документацию в соответствии со спецификацией OpenAPI, и естественным образом находите библиотеки, интегрированные с OpenAPI. Однако даже после принятия этого решения возникает следующая проблема:

> Библиотек для OpenAPI так много... Какую же выбрать...?

Этот документ представляет собой краткое введение в библиотеки, написанное для новичков в Go, которые сталкиваются с подобной ситуацией. Документ составлен по состоянию на конец 2024 года, и поскольку экосистема языка постоянно меняется, рекомендуется использовать его как справочный материал и регулярно следить за актуальными новостями.

### Стратегии библиотек в работе с OpenAPI

Вероятно, вам уже известно, что OpenAPI является спецификацией для четкого определения и документирования REST API. Определяя конечные точки API, форматы запросов и ответов в формате YAML или JSON, она помогает не только разработчикам, но и автоматизирует генерацию кода для фронтенда и бэкенда, что существенно сокращает бессмысленные повторения и уменьшает мелкие человеческие ошибки.

Чтобы естественным образом интегрировать OpenAPI в проект, библиотеки экосистемы Go, как правило, придерживаются следующих трех стратегий.

#### 1. Компоновка спецификации OpenAPI из комментариев Go

Одна из сложностей при разработке API в соответствии с OpenAPI заключается в том, что сама документация и код, который её реализует, хранятся в разных файлах и в совершенно разных местах, поэтому нередко возникает ситуация, когда код был обновлен, а документацию забыли обновить, или наоборот, документация обновлена, а код нет.

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

1. Вы изменили логику API в файле `./internal/server/user.go`
2. Сама документация находится в `./openapi3.yaml`, и вы можете по ошибке забыть внести в неё изменения.
3. Если вы не заметите эту проблему, создадите Pull Request и получите ревью от коллег,
4. то рецензенты тоже не увидят изменений в `./openapi3.yaml`, и может произойти досадная ситуация, когда спецификация API останется прежней, а реальная реализация API будет изменена.

Написание документации API в виде комментариев Go позволяет в известной степени устранить эту проблему. Поскольку код и документация находятся в одном месте, при изменении кода можно одновременно обновлять и комментарии. На основе этих комментариев существуют инструменты, которые автоматически генерируют спецификацию OpenAPI.

Одним из репрезентативных проектов является [Swag](https://github.com/swaggo/swag). Swag анализирует комментарии в коде Go и генерирует документ в формате OpenAPI 2. Использовать его просто: достаточно написать комментарии над функцией-обработчиком в формате, определенном библиотекой.

```go
// @Summary 유저 생성
// @Description 새로운 유저를 생성합니다.
// @Tags Users
// @Accept json
// @Produce json
// @Param user body models.User true "유저 정보"
// @Success 200 {object} models.User
// @Failure 400 {object} models.ErrorResponse
// @Router /users [post]
func CreateUser(c *gin.Context) {
    // ...
}
```

После того как комментарии написаны, CLI-инструмент Swag анализирует их и генерирует документ OpenAPI 2. Как правило, эта операция выполняется в рамках CI-процесса, а сгенерированная спецификация OpenAPI публикуется в Git-репозитории, в итоговых артефактах сборки или в отдельной внешней системе управления API-документацией, где её используют при совместной работе с другими проектами.

**Преимущества:**

- Поскольку комментарии находятся рядом с кодом, **вероятность расхождения реальной структуры кода и документации снижается.**
- Без дополнительных инструментов и сложной настройки, только с помощью комментариев, можно **просто и свободно вести документацию**.
- Поскольку комментарии не влияют на реальную логику API, **удобно добавлять временные функции**, публикация документации для которых нежелательна.

**Недостатки:**

- Из-за увеличения количества строк комментариев **может ухудшиться читаемость** отдельного файла с кодом.
- **Выразить всю спецификацию API** исключительно **в виде комментариев** может быть **затруднительно**.
- Документация не принуждает код к соответствию, поэтому **нельзя гарантировать, что документ OpenAPI и реальная логика совпадают.**

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

Существует также подход, при котором Single Source of Truth (SSOT) располагается не в коде Go, а в документе. Суть его в том, что сначала определяется спецификация OpenAPI, а затем на её основе генерируется код Go. Поскольку спецификация API сама генерирует код, можно обеспечить культуру разработки, в которой проектирование API происходит в первую очередь, а так как определение спецификации API становится самым первым этапом разработки, это позволяет заранее предотвратить ситуацию, когда упущенные моменты обнаруживаются уже после завершения разработки и весь код приходится переписывать вместе с изменением спецификации API.

Репрезентативными проектами, использующими этот подход, являются [oapi-codegen](https://github.com/oapi-codegen/oapi-codegen) и [OpenAPI Generator](https://github.com/OpenAPITools/openapi-generator/tree/master/samples/server/petstore/go-echo-server). Использовать их просто.

1. Напишите документ в формате yaml или json в соответствии со спецификацией OpenAPI
2. Запустите CLI
3. Будет сгенерирован соответствующий stub-код на Go
4. Теперь достаточно самостоятельно реализовать детальную логику для отдельных API, которую сможет использовать этот stub.

Ниже приведен пример кода, который генерирует oapi-codegen.

```go
// StrictServerInterface represents all server handlers.
type StrictServerInterface interface {
	// ...
	// Returns all pets
	// (GET /pets)
	FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
	// ...
}
```

Код, сгенерированный oapi-codegen на основе этого interface, выполняет разбор query-параметров, header и body, валидацию и вызывает соответствующий method, объявленный в interface. Пользователю достаточно реализовать интерфейс, и работа по реализации API будет завершена.

**Преимущества:**

- Поскольку **спецификация создается первой, а разработка идет после**, при совместной работе нескольких команд **задачи удобно выполнять параллельно**.
- Код, который раньше писался вручную в рамках **повторяющейся рутинной работы, генерируется **автоматически**, поэтому **эффективность работы возрастает**, при этом **отладка по-прежнему удобна**.
- Легко **гарантировать, что документация и код всегда соответствуют друг другу**.

**Недостатки:**

- При отсутствии знаний о самой спецификации OpenAPI **существует определенная крутая кривая обучения**.
- Поскольку структура кода, обрабатывающего API, генерируется автоматически в соответствии с проектом, **может быть сложно реагировать на случаи, требующие кастомизации**.

> Комментарий автора.
> По состоянию на октябрь 2024 года код, генерируемый OpenAPI Generator для Go, навязывает не только логику API, но и структуру всего проекта; поскольку архитектура становится жесткой, такой код плохо подходит для добавления различных функций, необходимых в реальной производственной среде. Тем, кто выбирает этот подход, настоятельно рекомендуется использовать oapi-codegen. Автор использует oapi-codegen + echo + StrictServerInterface.

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

Когда десятки и сотни людей разрабатывают один и тот же сервер, неизбежно возникает проблема: единообразие отдельных API может нарушаться. Наглядный пример: если описание более чем 100 конечных точек API объявлять в одном YAML-файле OpenAPI, этот файл неизбежно превратится в монстра из более чем 10 000 строк, и при добавлении новых конечных точек API модели будут неизбежно объявляться повторно, некоторые поля будут пропущены, а пути могут получать имена, не соответствующие конвенциям, в результате чего общая согласованность API начнет нарушаться.

Для решения этой проблемы можно назначить отдельного Owner, отвечающего за YAML-файлы OpenAPI, или разработать Linter, который будет автоматически выявлять такие проблемы в ходе CI. Однако можно также определить Domain-specific language (DSL) на языке Go и принудительно обеспечить единообразие всех API.

Репрезентативным проектом, использующим этот подход, является Kubernetes (он реализован собственными средствами без использования отдельных библиотек). Также можно попробовать такие проекты, как [go-restful](https://github.com/emicklei/go-restful) и [goa](https://goa.design/). Ниже приведен пример использования `goa`.

```go
var _ = Service("user", func() {
    Method("create", func() {
        Payload(UserPayload)
        Result(User)
        HTTP(func() {
            POST("/users")
            Response(StatusOK)
        })
    })
})
```

Написав такой компилируемый код на Go, вы одновременно получаете реализацию API `POST /users` и определение его документации.

**Преимущества:**

- Поскольку всё генерируется из кода, **легко обеспечить единообразие API во всем проекте**.
- Используя строгую типизацию Go, можно получить **более точную спецификацию, не вызывающую споров**, по сравнению с использованием всех возможностей OpenAPI 3.

**Недостатки:**

- **Необходимо изучить DSL, определенный каждым фреймворком**, и **его может быть сложно применить к существующему коду**.
- Поскольку необходимо строго следовать правилам, предложенным фреймворком, **гибкость и свобода могут снижаться**.

**Заключение**

У каждого метода есть свои преимущества и недостатки, и важно выбрать подходящий в соответствии с требованиями проекта и предпочтениями команды. Самое главное не то, какой способ лучше, а то, чтобы, оценив, какое решение наиболее подходит для вашей текущей ситуации, повысить продуктивность разработки и наслаждаться **ранним уходом с работы** и **удовлетворительным балансом между работой и личной жизнью**.

Хотя эта статья написана по состоянию на октябрь 2024 года, экосистемы Go и OpenAPI продолжают активно развиваться, поэтому, учитывая время, прошедшее с момента прочтения, рекомендуем и дальше следить за актуальным состоянием библиотек и проектов, а также за изменениями их преимуществ и недостатков.

Желаю счастливой жизни с Go~ 😘
