Модуль: Backend · Уровень: Middle+/Senior
TL;DR#
- OpenAPI — машиночитаемая спецификация (контракт) HTTP/REST API в YAML/JSON. До версии 3.0 называлась Swagger (2.0 == “Swagger 2.0”). Swagger сейчас — это набор инструментов (Swagger UI, Swagger Editor, Codegen) от SmartBear, а сам формат — OpenAPI Specification (OAS) под управлением OpenAPI Initiative (Linux Foundation).
- Версии: 2.0 (2014, Swagger) → 3.0 (2017, переработана структура:
components,requestBody,servers) → 3.1 (2021, полная совместимость с JSON Schema Draft 2020-12). - Два подхода: contract-first (design-first) — сначала пишем спеку, из неё генерим типы/сервер/клиент (
oapi-codegen); code-first — пишем Go-код с аннотациями, из него генерим спеку (swaggo/swag). Для контракта между командами/фронтом промышленный стандарт — contract-first. - Спека = source of truth. Контракт между бэкендом, фронтом, мобайлом, внешними потребителями. Версионируется в git, линтуется в CI (
spectral,vacuum), на breaking changes проверяется (oasdiff). - Валидация: статическая (линт спеки в CI) + рантайм (middleware на базе
kin-openapiвалидирует request/response против схемы).
Теория#
Что такое OpenAPI#
OpenAPI Specification (OAS) — формальное, языконезависимое описание HTTP API. Описывает: эндпоинты, методы, параметры, тела запросов/ответов, схемы данных, аутентификацию, примеры. Главная ценность — единый машиночитаемый контракт, из которого можно:
- генерировать серверные стабы и клиентов (десятки языков);
- генерировать документацию (Swagger UI, Redoc, Stoplight);
- валидировать трафик в рантайме;
- проверять обратную совместимость;
- использовать в mock-серверах (Prism) для параллельной разработки фронта и бэка.
История названий:
| Эра | Название формата | Кто ведёт |
|---|---|---|
| 2010–2015 | Swagger Specification | SmartBear |
| 2016+ | OpenAPI Specification (OAS) | OpenAPI Initiative (Linux Foundation) |
«Swagger 2.0» и «OpenAPI 2.0» — синонимы. С 3.0 используют только «OpenAPI».
Версии: 2.0 vs 3.0 vs 3.1#
| Аспект | OpenAPI 2.0 (Swagger) | OpenAPI 3.0 | OpenAPI 3.1 |
|---|---|---|---|
| Корневой ключ | swagger: "2.0" | openapi: 3.0.x | openapi: 3.1.x |
| Серверы | host + basePath + schemes | servers: [] (URL-шаблоны, переменные) | то же |
| Тело запроса | parameters с in: body | отдельный requestBody | то же |
| Переиспользуемые объекты | definitions, parameters, responses | единый components | то же |
| Несколько content-type | один на operation | content: {application/json: ..., ...} | то же |
| JSON Schema | подмножество Draft 4 (свои отклонения) | расширенное подмножество Draft 5/«Wright» | полный JSON Schema 2020-12 |
nullable | нет (хаки через x-nullable) | nullable: true | нет nullable; вместо него type: [string, "null"] (массив типов) |
type как массив | нет | нет | да (type: ["string","null"]) |
| Webhooks | нет | нет | да (webhooks) |
examples (множ.) | нет | да | да |
Произвольный $schema/$id | нет | ограниченно | да |
Ключевой сдвиг в 3.1 — это переход на JSON Schema 2020-12. Это значит, что схемы из OpenAPI 3.1 можно переиспользовать в обычных JSON Schema валидаторах и наоборот. Но это создаёт боль в тулинге: многие генераторы и валидаторы заточены под 3.0 и не понимают type: ["string","null"] или nullable-в-3.1-нет. На 2026 год часть production-инструментов всё ещё лучше работает с 3.0.x, поэтому версию выбирают исходя из зрелости тулчейна команды.
Структура спецификации#
Корневые объекты OpenAPI 3.x:
| Поле | Назначение |
|---|---|
openapi | версия спеки (строка, напр. 3.0.3) |
info | метаданные: title, version (версия API, не спеки), description, contact, license |
servers | список базовых URL (с переменными окружения) |
paths | объект «путь → операции» (get, post, put, patch, delete, …) |
components | переиспользуемые объекты: schemas, parameters, requestBodies, responses, securitySchemes, headers, examples |
security | глобальные требования безопасности (ссылки на securitySchemes) |
tags | группировка операций для документации |
Внутри operation: operationId (уникальный, критичен для кодогенерации — из него генерится имя метода), summary, parameters (path/query/header/cookie), requestBody, responses, security, tags.
parameters — каждый имеет name, in (path|query|header|cookie), required, schema. Для in: path всегда required: true.
requestBody — content с разбивкой по media-type, каждый со своей schema.
responses — ключи это HTTP-коды (200, 404, default), внутри description (обязателен!) + content + headers.
securitySchemes — типы: apiKey, http (basic/bearer), oauth2, openIdConnect, mutualTLS (3.1).
Пример YAML-спеки для CRUD-эндпоинта#
openapi: 3.0.3
info:
title: Users API
version: 1.2.0
description: CRUD over users
servers:
- url: https://api.example.com/v1
description: production
- url: http://localhost:8080/v1
description: local
security:
- bearerAuth: []
paths:
/users:
get:
operationId: listUsers
tags: [users]
summary: List users
parameters:
- name: limit
in: query
required: false
schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
- name: cursor
in: query
schema: { type: string }
responses:
'200':
description: page of users
content:
application/json:
schema:
$ref: '#/components/schemas/UserPage'
'401':
$ref: '#/components/responses/Unauthorized'
post:
operationId: createUser
tags: [users]
summary: Create user
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
'201':
description: created
headers:
Location:
schema: { type: string, format: uri }
content:
application/json:
schema: { $ref: '#/components/schemas/User' }
'400':
$ref: '#/components/responses/BadRequest'
'409':
description: email already exists
/users/{id}:
parameters:
- name: id
in: path
required: true
schema: { type: string, format: uuid }
get:
operationId: getUser
tags: [users]
responses:
'200':
description: user
content:
application/json:
schema: { $ref: '#/components/schemas/User' }
'404':
$ref: '#/components/responses/NotFound'
patch:
operationId: updateUser
tags: [users]
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/UpdateUserRequest' }
responses:
'200':
description: updated
content:
application/json:
schema: { $ref: '#/components/schemas/User' }
'404': { $ref: '#/components/responses/NotFound' }
delete:
operationId: deleteUser
tags: [users]
responses:
'204':
description: deleted
'404': { $ref: '#/components/responses/NotFound' }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
responses:
BadRequest:
description: validation error
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
Unauthorized:
description: missing/invalid token
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
NotFound:
description: resource not found
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
schemas:
User:
type: object
required: [id, email, createdAt]
properties:
id: { type: string, format: uuid }
email: { type: string, format: email }
name: { type: string, maxLength: 255 }
createdAt: { type: string, format: date-time }
CreateUserRequest:
type: object
required: [email]
properties:
email: { type: string, format: email }
name: { type: string, maxLength: 255 }
UpdateUserRequest:
type: object
properties:
name: { type: string, maxLength: 255 }
UserPage:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/User' }
nextCursor: { type: string }
Error:
type: object
required: [code, message]
properties:
code: { type: string }
message: { type: string }
details:
type: array
items: { type: string }Contract-first vs code-first#
Contract-first (design-first): спека — артефакт, который пишут (часто фронт + бэк вместе) до кода. Из неё генерируют типы, server interface, клиентов. Спека ревьюится в PR как любой код.
Code-first: истина — Go-код с аннотациями (swaggo/swag) или со структурными тегами; спека генерируется из кода как побочный продукт.
| Критерий | Contract-first | Code-first |
|---|---|---|
| Source of truth | YAML-спека | Go-код / аннотации |
| Параллельная работа фронт/бэк | отлично (mock из спеки сразу) | плохо (нужен код) |
| Риск дрейфа кода и спеки | низкий (код генерится из спеки) | высокий (аннотации забывают обновить) |
| Гарантия валидности спеки | высокая | спека может не валидироваться, генерится «как есть» |
| Барьер входа | выше (надо знать OAS) | ниже (пишешь привычный Go) |
| Контроль над сгенерированным кодом | средний | полный (код твой) |
| Кросс-командный контракт | сильная сторона | слабая |
| Документация | первична | вторична, легко отстаёт |
| Типичный инструмент в Go | oapi-codegen | swaggo/swag |
Senior-выбор: для публичного/межкомандного API — contract-first. Для внутреннего сервиса, который никто кроме тебя не дёргает, и где спека нужна лишь для доки — допустим code-first, но осознавая риск дрейфа.
Кодогенерация в Go#
oapi-codegen (contract-first)#
oapi-codegen (форк/наследник deepmap/oapi-codegen, сейчас под oapi-codegen/oapi-codegen) генерирует из OpenAPI 3.x:
- типы (Go-структуры из
components/schemas); - server interface под нужный роутер:
chi,echo,gin,net/http(std-http),fiber,gorilla; - HTTP-клиент (типизированные методы);
- встроенную спеку (
embedded-spec) для рантайм-валидации; - strict server — обёртка, где хендлеры принимают типизированный request-объект и возвращают типизированный response (роутер сам парсит/сериализует).
Конфиг (oapi-codegen.yaml):
# oapi-codegen.yaml
package: api
output: internal/api/api.gen.go
generate:
models: true
chi-server: true # сгенерировать ServerInterface под chi
strict-server: true # строгий типизированный слой поверх
embedded-spec: true
output-options:
skip-prune: false
user-templates: {} # можно подменять шаблоныЗапуск через go generate:
//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=oapi-codegen.yaml openapi.yamlСгенерированный ServerInterface (упрощённо, для chi):
// api.gen.go (фрагмент)
type ServerInterface interface {
// (GET /users)
ListUsers(w http.ResponseWriter, r *http.Request, params ListUsersParams)
// (POST /users)
CreateUser(w http.ResponseWriter, r *http.Request)
// (GET /users/{id})
GetUser(w http.ResponseWriter, r *http.Request, id openapi_types.UUID)
// (PATCH /users/{id})
UpdateUser(w http.ResponseWriter, r *http.Request, id openapi_types.UUID)
// (DELETE /users/{id})
DeleteUser(w http.ResponseWriter, r *http.Request, id openapi_types.UUID)
}
type ListUsersParams struct {
Limit *int `form:"limit,omitempty"`
Cursor *string `form:"cursor,omitempty"`
}
// Регистрирует роуты в chi.Router и вешает обработчики на ServerInterface.
func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { /* ... */ }Со strict-server интерфейс становится чище — без http.ResponseWriter:
type StrictServerInterface interface {
ListUsers(ctx context.Context, request ListUsersRequestObject) (ListUsersResponseObject, error)
CreateUser(ctx context.Context, request CreateUserRequestObject) (CreateUserResponseObject, error)
GetUser(ctx context.Context, request GetUserRequestObject) (GetUserResponseObject, error)
// ...
}
// Имплементация:
func (s *Service) CreateUser(ctx context.Context, req CreateUserRequestObject) (CreateUserResponseObject, error) {
u, err := s.users.Create(ctx, req.Body.Email, deref(req.Body.Name))
if err != nil {
if errors.Is(err, ErrEmailTaken) {
return CreateUser409JSONResponse{Error{Code: "conflict", Message: "email exists"}}, nil
}
return nil, err // 500
}
return CreateUser201JSONResponse(toAPIUser(u)), nil
}Тонкости oapi-codegen:
operationIdобязателен и должен быть уникальным — иначе имена методов конфликтуют/мусорные.- Управление маппингом типов:
x-go-type,x-go-type-importдля подмены сгенерированного типа на свой (напр. кастомныйMoney,decimal.Decimal). nullablevsrequiredопределяет, генерится поле как*TилиT. Отсутствиеrequired→ указатель/omitempty.additionalProperties: true→map[string]interface{}(или генерится спец-тип сAdditionalProperties).oneOf/anyOfгенерируются в union-тип с методамиAs.../Merge...— работать с ними неудобно, на senior это частый разговор о моделировании.
swaggo/swag (code-first)#
Генерирует Swagger 2.0 (не 3.x!) из аннотаций в комментариях Go. Это его принципиальное ограничение — для 3.x придётся конвертировать.
// @Summary Create user
// @Tags users
// @Accept json
// @Produce json
// @Param body body CreateUserRequest true "payload"
// @Success 201 {object} User
// @Failure 400 {object} Error
// @Router /users [post]
func (h *Handler) CreateUser(c *gin.Context) { /* ... */ }swag init парсит AST и комментарии → docs/swagger.json + swagger.yaml + Go-пакет docs. Минусы: спека вторична, аннотации легко рассинхронизировать с реальным поведением, ограничены Swagger 2.0, нет строгой типобезопасности контракта.
Сравнение генераторов#
| Инструмент | Подход | OAS-версия | Что генерит |
|---|---|---|---|
oapi-codegen | contract-first | 3.0/3.1 | типы, server iface, client, strict-server, embedded spec |
swaggo/swag | code-first | 2.0 | спека из аннотаций + Swagger UI |
ogen | contract-first | 3.0/3.1 | типы + сервер + клиент, без рефлексии, встроенная валидация, OTEL |
go-swagger | оба | 2.0 | сервер/клиент (тяжёлый, legacy на 2.0) |
deepmap/oapi-codegen — старое имя; проект переехал в org oapi-codegen, импорт-путь github.com/oapi-codegen/oapi-codegen/v2. На собеседовании упоминание «deepmap» — маркер того, что человек давно с ним работает.
Валидация#
Два уровня:
1. Статическая (CI, линт спеки):
- Spectral (Stoplight) — линтер OpenAPI/AsyncAPI/JSON Schema с настраиваемыми правилами (
.spectral.yaml): обязательныеoperationId, описания, запретadditionalPropertiesбез схемы, нейминг-конвенции и т.д. - vacuum — быстрый (Go) линтер, совместим с правилами Spectral, удобен для больших спек в CI.
# .spectral.yaml
extends: ["spectral:oas"]
rules:
operation-operationId: error
operation-description: warn
oas3-unused-component: warn2. Рантайм (request/response validation middleware):
kin-openapi (getkin/kin-openapi) — Go-библиотека: парсит спеку (openapi3.Loader), строит роутер (gorillamux/legacy), валидирует входящие запросы и (опционально) исходящие ответы против схемы.
loader := openapi3.NewLoader()
doc, _ := loader.LoadFromFile("openapi.yaml")
_ = doc.Validate(loader.Context) // проверка самой спеки
router, _ := gorillamux.NewRouter(doc)
func ValidatorMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
route, pathParams, err := router.FindRoute(r)
if err != nil { http.Error(w, "not found in spec", 404); return }
reqInput := &openapi3filter.RequestValidationInput{
Request: r, PathParams: pathParams, Route: route,
}
if err := openapi3filter.ValidateRequest(r.Context(), reqInput); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest); return
}
next.ServeHTTP(w, r)
})
}oapi-codegen интегрируется с kin-openapi: генерируемый embedded-spec + middleware oapimiddleware.OapiRequestValidator(swagger) даёт авто-валидацию запросов против встроенной спеки.
Response-валидация обычно включают только в тестах/staging (на проде дорого и рискованно ронять валидный для клиента ответ из-за расхождения схемы).
Source of truth и кросс-командное взаимодействие#
Спека — контракт. Практики senior-уровня:
- Спека живёт в git (отдельный репо
api-specsили рядом с сервисом), ревьюится в PR. - Из неё генерится: серверный код бэка, типизированные клиенты (фронт через
openapi-typescript/orval, другие сервисы черезoapi-codegen). - Mock-сервер (Prism,
stoplight/prism) поднимается из спеки — фронт пилит UI, не дожидаясь бэка. - Документация (Redoc/Swagger UI) генерится автоматически — всегда актуальна.
- Single source of truth убирает класс багов «фронт ждёт одно, бэк отдаёт другое».
Версионирование и breaking changes#
Версионирование API (не путать с версией спеки openapi:):
- В пути:
/v1/users,/v2/users— самый явный, грубый. - В заголовке:
Accept: application/vnd.api+json; version=2или кастомныйX-API-Version. - Поле
info.version(semver) — версия документа/API.
Breaking vs non-breaking:
| Non-breaking (backward compatible) | Breaking |
|---|---|
| добавление нового optional-поля в ответ | удаление поля из ответа |
| новый эндпоинт | удаление/переименование эндпоинта |
| новый optional query-параметр | новый required параметр |
| расширение enum в ответе (спорно для клиентов) | сужение enum, смена типа поля |
| ослабление валидации входа | ужесточение валидации входа, новый required в body |
oasdiff — инструмент для diff двух версий спеки и детекта breaking changes; ставят в CI как gate на PR:
oasdiff breaking old.yaml new.yaml --fail-on ERR
# выводит классифицированные изменения; ненулевой код → блокируем mergeТак контракт защищён: нельзя случайно сломать потребителей. Для намеренных breaking changes — бамп мажорной версии API и план миграции (deprecation через deprecated: true + Sunset/Deprecation заголовки).
Подводные камни / gotchas#
- 3.1 ≠ просто «новее»: переход на JSON Schema 2020-12 ломает совместимость с тулингом, заточенным под 3.0.
nullableв 3.1 нет — вместо негоtype: ["string","null"]. Многие генераторы/UI на 2026 ещё неполно поддерживают 3.1. swaggo/swagотдаёт Swagger 2.0, а не OpenAPI 3.x. Если в требованиях «OpenAPI 3», то либоoapi-codegen/ogen(contract-first), либо конвертация 2.0→3.0 (теряются нюансы).- Дрейф code-first: аннотации забывают обновить — спека и код расходятся, документация врёт. В contract-first код генерится, дрейф невозможен (но можно забыть перегенерить — добавляют CI-проверку «generated up to date»).
operationIdколлизии/отсутствие → уoapi-codegenмусорные/конфликтующие имена методов. Делать его обязательным правилом Spectral.required≠ nullable.requiredговорит «поле присутствует», nullable — «значение может быть null». В Go:required && !nullable→T; иначе →*T/omitempty. Путаница ведёт к багам сериализации (отправкаnullvs отсутствие ключа).additionalProperties: по умолчанию в JSON Schema разрешены любые доп. поля. Если хочешь strict — явноadditionalProperties: false. Иначе валидация пропустит мусор; в Go генеритсяmap, теряется типобезопасность.oneOf/anyOf/allOf— мощно в схеме, но в Go генерируются в неудобные union-типы.allOfчасто (неправильно) используют как «наследование/композицию»; для дискриминируемых union нуженdiscriminator.- Response-валидация в рантайме на проде может уронить корректный для клиента ответ из-за расхождения схемы. Держать в тестах/staging.
format(email,uuid,date-time) — это аннотация; не все валидаторы его проверяют, и не все генераторы мапят в нужный Go-тип. Проверять, что выбранный тулчейн действительно валидирует format.$refи циклические ссылки: глубокие/циклические$refломают часть генераторов; внешние$ref(на другие файлы/URL) поддерживаются неравномерно — часто бандлят в один файл (redocly bundle).- Версия спеки vs версия API:
openapi: 3.0.3— версия формата;info.version— версия твоего API. Их регулярно путают. - embedded-spec и расхождение: если валидируешь по встроенной (на момент сборки) спеке, а деплоят другую — поведение разойдётся; держать единый источник.
Вопросы на собеседовании#
В: В чём разница между Swagger и OpenAPI? О: Swagger — историческое название формата (версия 2.0 == «Swagger 2.0») и набор инструментов SmartBear (Swagger UI/Editor/Codegen). С версии 3.0 формат называется OpenAPI Specification и ведётся OpenAPI Initiative под Linux Foundation. То есть OpenAPI — это спецификация, Swagger сегодня — тулинг вокруг неё.
В: Что принципиально нового в OpenAPI 3.1 по сравнению с 3.0?
О: Полная совместимость с JSON Schema Draft 2020-12. Исчез nullable (теперь type: ["string","null"]), type может быть массивом, появились webhooks, поддержка $schema/$id, mutualTLS. Минус — часть тулинга ещё не догнала 3.1, поэтому переход не бесплатный.
В: Contract-first vs code-first — что выберешь для API между несколькими командами и почему? О: Contract-first. Спека — единый source of truth, ревьюится отдельно, из неё генерится сервер и типизированные клиенты для всех потребителей, можно сразу поднять mock-сервер для параллельной разработки фронта. Code-first (swaggo) удобен для внутреннего сервиса, но спека вторична и склонна к дрейфу с кодом.
В: Что генерирует oapi-codegen и чем strict-server отличается от обычного?
О: Генерирует Go-типы из components/schemas, ServerInterface под выбранный роутер (chi/echo/gin/std-http), HTTP-клиент, опционально embedded-spec. Обычный server interface даёт методы с http.ResponseWriter/*http.Request — парсинг/сериализация на тебе. Strict-server генерирует слой, где хендлер принимает типизированный RequestObject и возвращает типизированный ResponseObject (напр. CreateUser201JSONResponse), а инфраструктурный код сам парсит вход и сериализует выход — меньше boilerplate и ошибок.
В: Почему свежий проект на «OpenAPI 3» нельзя делать на swaggo/swag?
О: swaggo/swag генерирует Swagger 2.0, а не OpenAPI 3.x. Для 3.x нужен contract-first генератор (oapi-codegen, ogen) либо конвертация 2.0→3.0 с потерей нюансов. Плюс swaggo — code-first, спека вторична и дрейфует.
В: Как валидировать запросы против OpenAPI-спеки в рантайме в Go?
О: Через kin-openapi: загрузить и провалидировать спеку (openapi3.Loader, doc.Validate), построить роутер (gorillamux), в middleware найти маршрут (FindRoute) и вызвать openapi3filter.ValidateRequest. С oapi-codegen это упрощается через embedded-spec и OapiRequestValidator. Response-валидацию обычно держат в тестах/staging, не на проде.
В: Чем отличаются required и nullable и как это влияет на сгенерированный Go-код?
О: required — поле обязано присутствовать в объекте; nullable — значение может быть null. Это ортогонально. В Go: required и не nullable → значение по значению (T); иначе → указатель (*T) с omitempty. Путаница приводит к багам: отправка null против отсутствия ключа, неверная (де)сериализация.
В: Как в CI не дать сломать обратную совместимость API?
О: Линт спеки (spectral/vacuum) на качество и конвенции + diff против предыдущей версии (oasdiff breaking old new --fail-on ERR), который классифицирует изменения и блокирует merge при breaking. Breaking-изменения требуют бампа мажорной версии API и плана миграции (deprecated: true, Sunset/Deprecation заголовки).
В: Что считается breaking change в REST API? О: Удаление/переименование эндпоинта или поля ответа, добавление нового required-параметра/поля в запрос, ужесточение валидации входа, смена типа поля, сужение enum. Non-breaking: новый эндпоинт, новое optional-поле в ответе, новый optional-параметр, ослабление валидации.
В: Как подменить сгенерированный oapi-codegen тип на свой (например, decimal вместо float)?
О: Расширениями x-go-type и x-go-type-import прямо в схеме, либо через секцию маппинга типов в конфиге. Это позволяет отдать в код, скажем, decimal.Decimal вместо float64 для денежных сумм, сохранив контракт в спеке.
На что копают на senior+#
- Моделирование union/полиморфизма:
oneOf/anyOf/allOf+discriminator, как это ложится на Go (генерируемые union-типы,As/Mergeметоды), когда лучше денормализовать схему ради удобства кода. - Эволюция контракта: стратегия версионирования (path vs header vs media-type), deprecation policy,
Sunsetheader (RFC 8594), как катить breaking changes без даунтайма потребителей. - CI-гейтинг контракта: связка spectral/vacuum + oasdiff + проверка «generated code up to date» (перегенерить и
git diff --exit-code), запрет merge при дрейфе/breaking. - Где валидировать: статика vs рантайм vs только-тесты; стоимость response-валидации на проде; что делать при расхождении embedded-spec и задеплоенной спеки.
- Тулчейн-зрелость 3.0 vs 3.1: осознанный выбор версии под возможности генераторов/валидаторов команды, а не «берём новее».
- Организация спек в масштабе: монорепо vs отдельный
api-specsрепо, бандлинг$ref(redocly bundle), переиспользование общих схем (error-модель, pagination) между сервисами. - Контракт как граница команд: mock-сервера (Prism) для параллельной разработки, генерация клиентов фронта (
openapi-typescript/orval), consumer-driven contract testing (Pact) как дополнение к схеме. - Безопасность в спеке: корректное описание
securitySchemes(oauth2 flows, scopes), что спека описывает контракт, но не заменяет реальную проверку прав; не утечь в публичную доку внутренние эндпоинты. - Производительность валидации: стоимость рантайм-валидации тяжёлых тел, кэширование скомпилированных схем, выбор
ogen(без рефлексии) для горячих путей.