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

В этом практическом руководстве я хочу познакомить вас со всеми важными аспектами, связанными с разработкой, внедрением и использованием API основываясь на стандартах open API. Для этого практического руководства требуется понимание в следующих темах:
программирования на Golang
RESTful APIs
JSON/YAML
Базовое владение командной строки
Что такое OpenAPI Specification (OAS)
OpenAPI Specification (OAS) — это стандарт описания HTTP API, который позволяет единообразно передавать информацию на всех этапах жизненного цикла API — от проектирования до эксплуатации. Он задаёт структуру и синтаксис API независимо от того, на каком языке программирования реализован сам сервис.
Интересно, что OpenAPI Specification (OAS) выросла из практического инструмента. Изначально она была основана на спецификации Swagger 2.0, созданной компанией SmartBear Software. Позже, чтобы развивать стандарт как независимый и открытый, его передали в управление OpenAPI Initiative (OAI) — консорциуму отраслевых экспертов под эгидой Linux Foundation.
Основная идея OpenAPI — описывать API в независимом от конкретного языка программирования виде. Это позволяет отделить описание API от реализации. Потребителям вашего API не нужно разбираться в деталях вашей системы или, скажем, учить Lisp или Haskell, если вы вдруг решили написать сервис именно на них. Всё, что им нужно, они найдут в спецификации API, представленной на простом и понятном языке.
Этот простой и выразительный язык называется DSL (domain-specific language, предметно-ориентированный язык). Спецификацию можно описывать как в JSON, так и в YAML.
На текущий момент последняя версия OAS — v3.1.1. Спецификация довольно объёмная — в ней масса возможностей и тонкостей. Но в этой статье мы сосредоточимся на самых важных и полезных моментах.
Проектируем API
Всё начинается с понимания того, какие возможности ваш API должен предоставлять потребителям и для чего он вообще создаётся. На этом этапе работа не всегда чисто техническая, но если уже в процессе сбора требований вы начнёте набрасывать черновик спецификации OAS, это серьёзно ускорит последующую разработку.
Когда требования сформулированы, самое время открыть редактор OpenAPI и подключить коллег к обсуждению.
Важно помнить, что дело тут не только в том, чтобы просто «написать JSON или YAML». Главная цель — прийти к согласованному дизайну API, понятному всем участникам команды.
Очень рекомендую опираться на какой-нибудь гайд по проектированию API. Например, можно взять руководство от Google. Это поможет вам избежать типичных проблем — вроде смешанного стиля (/resourceName/{id} и /resource_name/{id}), неконсистентного использования HTTP-методов или неочевидных связей между ресурсами.
Файл openapi.yaml
Спецификация вашего API обычно начинается с файла openapi.yaml (такое имя рекомендуется, но не обязательно) или openapi.json. В реальных проектах я встречал огромные openapi.yaml — по 50 тысяч строк. Но спецификацию можно и удобно разбивать на несколько файлов. Правда, стоит учитывать, что некоторые инструменты OpenAPI работают корректно только с «монолитной» спецификацией в одном файле. Хороший пример такого подхода — Google Maps OAS: схема разбита на части, но используется специальный препроцессор для сборки итогового файла.
Для этого есть несколько open source инструментов. Например:
swagger-cli (архивирован, но всё ещё полезен)
redocly-cli — отличная современная альтернатива
Пример команды для сборки спецификации в единый файл:
swagger-cli bundle -o _bundle/openapi.yaml openapi.yaml
Как я уже упоминал, спецификация может быть довольно объёмной, поэтому лучше разбирать её по частям. В качестве примера для этой статьи я подготовил тестовый API для «Умного дома». Полный пример спецификации и код можно посмотреть здесь.
Структура OpenAPI Object
Корневой объект спецификации называется OpenAPI Object. Его базовая структура выглядит примерно так:
# версия схемы
openapi: 3.1.1
# информация о документации
info:
title: Smart Home API
description: API Specification for Smart Home API
version: 0.0.1
# (опционально) список серверов — для публичных API
servers:
- url: "https://..."
# теги для группировки эндпоинтов
tags:
- name: device
description: Manage devices
- name: room
description: Manage rooms
# здесь описываются эндпоинты
paths:
# ...
# переиспользуемые объекты: схемы, типы ошибок, тела запросов и т.п.
components:
# ...
# механизмы безопасности (должны соответствовать components.securitySchemes)
security:
- apiKeyAuth: []
Мы определили каркас нашей схемы. Однако основная часть спецификации OpenAPI обычно сосредоточена в полях paths и components — именно там описываются все эндпоинты и переиспользуемые элементы.
Paths и Operations
Теперь добавим в нашу спецификацию несколько эндпоинтов. В OpenAPI операции группируются по пути (path), и на одном пути можно описать несколько HTTP-методов. Например:
GET /devices/{deviceId}
DELETE /devices/{deviceId}
Хорошей практикой считается выносить все типы данных (тела запросов, ответы, ошибки) в секцию components и ссылаться на них из секции paths, а не дублировать описание прямо в paths. Это облегчает переиспользование сущностей. В нашем примере у нас есть тип Device, который может понадобиться во многих эндпоинтах.
paths:
# путь с параметром
/devices/{deviceId}:
get:
tags:
- device
summary: Get Device
operationId: getDevice
parameters:
- name: deviceId
in: path
required: true
schema:
$ref: "#/components/schemas/ULID"
responses:
"200":
description: Success
content:
application/json:
schema:
$ref: "#/components/schemas/Device"
"404":
description: Not Found
content:
application/json:
schema:
# общий тип для ошибок 404
$ref: "#/components/schemas/ErrorNotFound"
В этом примере мы описали два возможных ответа для эндпоинта GET /devices/{deviceId} и сослались на ещё не определённые типы: Device, ErrorNotFound и ULID.
Обрати внимание: для параметра deviceId мы используем кастомный тип ULID, а не обычную строку. Это даёт гибкость — в будущем мы сможем поменять формат идентификаторов (например, на UUID, ULID, integer и т.п.) без необходимости переписывать спецификацию.
Также стоит отметить, что каждая операция имеет уникальный operationId. Это поле не является обязательным, но его очень полезно указывать — оно помогает на серверной стороне и при генерации клиентского кода.
Это базовый пример, который можно легко расширять. Например, при использовании схемы в Swagger UI бывает удобно показывать примеры запросов и ответов (включая разные варианты). Для этого можно добавить секцию examples прямо в responses, либо определить примеры в components.schemas.
responses:
"200":
content:
application/json:
examples:
new_device:
value: # любой пример значения
Schemas
Секция components — это важнейшая часть спецификации OAS. В ней можно определить переиспользуемые элементы. Основные свойства components:
schemas — схемы данных (модели)
responses — стандартные ответы
parameters — параметры запроса
requestBodies — тела запросов
headers — заголовки
securitySchemes — схемы безопасности
Полный список можно посмотреть в документации.
Допустим, мы хотим описать нашу сущность Device. В простом случае это можно сделать так:
components:
schemas:
Device:
type: object
properties:
id:
$ref: '#/components/schemas/ULID'
name:
type: string
required:
- id
- name
Но на практике бывает, что у разных сущностей появляются одинаковые поля (например, id, name, createdAt, updatedAt и т.п.). Чтобы не дублировать их в каждой схеме, рекомендуется выносить общие части в отдельные схемы и затем комбинировать их с помощью allOf.
components:
schemas:
WithId:
type: object
required:
- id
properties:
id:
$ref: "#/components/schemas/ULID"
WithName:
type: object
required:
- name
properties:
name:
type: string
Device:
allOf:
- $ref: "#/components/schemas/WithId"
- $ref: "#/components/schemas/WithName"
Техники allOf, oneOf, anyOf — это очень мощные инструменты для гибкого моделирования схем в OpenAPI. Они позволяют:
строить наследование между типами;
описывать варианты данных (например, разные форматы ответа в зависимости от условий);
комбинировать базовые сущности в более сложные.
Extensions
В спецификацию OpenAPI можно добавлять расширения — специальные свойства, которые не влияют на саму схему API, но полезны для генераторов серверного или клиентского кода. Такие поля обычно начинаются с префикса x-.
Простой пример — наш тип ULID для идентификаторов:
ULID:
type: string
minLength: 26
maxLength: 26
# пример значения — будет отображаться в Swagger UI
example: 01ARZ3NDEKTSV4RRFFQ69G5FAV
# кастомные свойства для генератора Go-кода
x-go-type: ulid.ULID
x-go-type-import:
path: github.com/oklog/ulid/v2
В данном случае:
поле example позволяет сразу показать пример значения в документации (например, в Swagger UI).
кастомные поля x-go-type и x-go-type-import используются генератором серверного кода на Go. Вместо того чтобы автоматически сгенерировать новый тип для ULID, он будет использовать готовый тип ulid.ULID из пакета github.com/oklog/ulid/v2.
Таким образом, расширения позволяют добавить дополнительную “метаинформацию”, которая облегчает интеграцию с вашими реальными типами данных и улучшает автогенерацию кода.
Генерация Go-сервера
Мы рассмотрели здесь далеко не все возможные свойства схемы — только основные. Но теперь у вас должно сложиться общее понимание, как устроен OpenAPI Specification. Полную спецификацию можно почитать здесь.
А теперь, когда наша схема готова, давайте попробуем сгенерировать сервер на Go.
Список доступных генераторов можно найти на openapi.tools. Их довольно много. Самый популярный генератор для Go — это oapi-codegen.
Установить генератор можно командой:
go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest
Конфигурация
Настроить генератор очень просто. Параметры можно передавать либо прямо в командной строке, либо через конфигурационный файл YAML.
В конфигурации можно указать:
какой HTTP-роутер использовать для сервера (например, echo, chi, gin);
куда класть сгенерированный код;
какие части генерировать (только модели, только сервер, клиент и т.п.).
В нашем примере будем использовать роутер echo. Пример oapi-codegen.yaml:
# oapi-codegen.yaml
package: api
output: pkg/api/api.gen.go
generate:
strict-server: true
models: true
echo-server: true
Генерация кода
oapi-codegen --config=oapi-codegen.yaml openapi.yaml
Что будет сгенерировано
Посмотрим, что появляется в файле api.gen.go.
Так как мы включили опцию strict-server, генератор создаст интерфейс, который парсит тела запросов и автоматически сериализует ответы.
Интерфейс будет выглядеть примерно так:
type StrictServerInterface interface {
// List Devices
// (GET /devices)
ListDevices(ctx context.Context, request ListDevicesRequestObject) (ListDevicesResponseObject, error)
// Get Device
// (GET /devices/{deviceId})
GetDevice(ctx context.Context, request GetDeviceRequestObject) (GetDeviceResponseObject, error)
}
Также будут сгенерированы все используемые типы:
type ULID = ulid.ULID
type Device struct {
Id ULID `json:"id"`
Name string `json:"name"`
}
Кроме того, будет создан код для автоматического парсинга запросов и генерации Swagger-спецификации.
Имплементация
Осталось совсем немного — поднять сервер на Echo, реализовать сгенерированный интерфейс и связать всё вместе.
Пример кода для файла pkg/api/impl.go:
package api
import "context"
type Server struct{}
func NewServer() Server {
return Server{}
}
func (Server) ListDevices(ctx context.Context, request ListDevicesRequestObject) (ListDevicesResponseObject, error) {
// здесь будет реальная логика
return ListDevices200JSONResponse{}, nil
}
func (Server) GetDevice(ctx context.Context, request GetDeviceRequestObject) (GetDeviceResponseObject, error) {
// здесь будет реальная логика
return GetDevice200JSONResponse{}, nil
}
Здесь я опустил саму реализацию логики, просто показал, как возвращаются ответы. Удобно, что oapi-codegen уже сгенерировал для нас все возможные типы ответов — не нужно писать их вручную.
Теперь остаётся только поднять сам Echo-сервер. Обратите внимание — ручной обработчик эндпоинтов больше не нужен, вся работа по разбору запросов и сериализации ответов уже реализована в сгенерированном коде.
Единственное, о чём стоит помнить — в методах реализации всё равно нужно делать валидацию входящих данных.
Пример файла main.go:
package main
import (
"oapiexample/pkg/api"
"github.com/labstack/echo/v4"
)
func main() {
server := api.NewServer()
e := echo.New()
api.RegisterHandlers(e, api.NewStrictHandler(
server,
// сюда можно добавить middlewares, если нужно
[]api.StrictMiddlewareFunc{},
))
e.Start("127.0.0.1:8080")
}
Теперь можно запустить сервер:
go run .
И проверить его, например, командой:
curl http://localhost:8080/devices
Вот и всё — мы сгенерировали сервер по OpenAPI спецификации, реализовали интерфейс и подняли рабочий сервер с готовыми эндпоинтами.
Генератор oapi-codegen поддерживает множество веб-фреймворков и серверов на Go:
Chi
Fiber
Gin
а также стандартный net/http
Это позволяет легко интегрировать его в существующую архитектуру проекта.
Как визуализировать API-документацию
Иногда бывает полезно вместе с API раздавать документацию в виде Swagger UI — например, для тестирования или в качестве публичной документации.
Сам oapi-codegen не генерирует Swagger UI «из коробки», но это легко добавить вручную. Можно создать простую HTML-страницу с подключённым Swagger JS, которая будет загружать нашу OpenAPI спецификацию.
Пример такой страницы — pkg/api/index.html — можно посмотреть здесь
Встраиваем Swagger UI в Go-приложение
Чтобы раздавать статику прямо из Go-приложения, удобно использовать директиву go:embed.
//go:embed pkg/api/index.html
//go:embed openapi.yaml
var swaggerUI embed.FS
func main() {
// ...
// раздаём Swagger UI
e.GET("/swagger/*", echo.WrapHandler(http.StripPrefix("/swagger/", http.FileServer(http.FS(swaggerUI)))))
}
Теперь достаточно запустить сервер и открыть в браузере:
http://localhost:8080/swagger/
Вы увидите Swagger UI, автоматически подгружающий вашу спецификацию OAS.

Использование Postman
Такие инструменты, как Postman, сегодня очень популярны для работы с API-документацией.
Важно, что Postman поддерживает импорт готовых спецификаций OpenAPI 3.0 и 3.1 — как в формате YAML, так и в JSON. Это позволяет легко загружать уже существующую схему API для тестирования или демонстрации.
Генерация OAS из кода
Существует и другой подход — генерировать спецификацию OpenAPI прямо из кода. Особенно это распространено в языках с сильной типизацией.
Идея в том, что если схема «живёт рядом с кодом», разработчики будут чаще её актуализировать.
На практике, правда, это работает далеко не всегда. Именно поэтому такой подход постепенно теряет популярность. Лично я тоже не большой фанат генерации схем «из кода», так как не видел от неё серьёзной пользы.
Но если вам интересно попробовать — вот несколько проектов:
Генерация клиентского кода
Как я уже упоминал, OpenAPI отлично подходит для организации взаимодействия между командами. Главное — корректно версионировать вашу спецификацию (см. поле info.version) и распространять её между командами.
Этот процесс можно частично автоматизировать — например, упаковывать схему и выкладывать её в общий доступ. В проектах часто используют:
Git submodules
GitHub Actions для публикации версий схем
Допустим, у нас есть фронтенд-приложение на TypeScript (что довольно часто встречается при разработке веб-API). Для генерации клиентского кода тоже есть масса инструментов — см. список на openapi.tools.
Самый популярный — openapi-typescript.
Примеры генерации:
# Локальная схема
npx openapi-typescript openapi.yaml -o ./client/schema.d.ts
# Схема с удалённого сервера
npx openapi-typescript https://.../openapi.yaml -o ./client/schema.d.ts
Заключение
Сегодня OpenAPI — это де-факто стандарт для проектирования, реализации и потребления REST API. Поэтому крайне важно понимать, как он работает.
Надеюсь, эта статья дала вам полезное введение в спецификацию OpenAPI, а также практические советы и примеры того, как можно использовать OAS для проектирования, реализации и использования API.
roodewald
Спасибо, это исчерпывающее руководство по работе с OpenAPI.
До этого момента мне не попадалась статья, которая бы провела меня от проектирования и до реализации.