---
title: "Отражение и документация API"
description: "Документация API в Connect: proto-комментарии как единый источник, protoc-gen-doc и OpenAPI из buf generate, дескрипторы и gRPC-рефлексия живого сервера."
course: "Connect: современный RPC от Buf"
url: "https://golangmaster.ru/courses/connect/lessons/15-dokumentaciya/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 18
---

# Отражение и документация API

*Курс «Connect: современный RPC от Buf», урок 15 из 25. ~18 минут. Обновлено 21.09.2026. Источник: https://golangmaster.ru/courses/connect/lessons/15-dokumentaciya/*

Документация API — та часть работы, которую все любят откладывать и проклинают, когда она устарела. В Connect-проекте документация protobuf не ведётся отдельно: единственным источником правды остаются proto-файлы, а всё остальное — markdown-доки, OpenAPI-контракт, рефлексия живого сервера — генерируется из них тем же buf-конвейером, который вы собрали в [одиннадцатом уроке](/courses/connect/lessons/11-buf-generate/). Разбираем, как комментарии над service и rpc превращаются в Go-доккомментарии и человекочитаемые страницы, что такое дескрипторы и почему схему можно читать из самого Go, как работает gRPC-рефлексия с пакетом grpcreflect, и зачем OpenAPI рядом с proto, если на дворе 2025-й. По дороге закроем и коммерческий интерес: по запросам вроде api docs grpc и connect документация поисковик приводит людей, которым нужен работающий контракт, а не маркетинг — и у вас он будет генерироваться сам.

К этому уроку у вас собран полный buf-конвейер: lint из [десятого урока](/courses/connect/lessons/10-buf-lint/), генерация из одиннадцатого, публикация модулей в BSR из [двенадцатого](/courses/connect/lessons/12-bsr/). Не хватает одной ступени — документации, и мы добавим её туда же, в buf.gen.yaml, чтобы docs обновлялись тем же коммитом, что и код. Принцип урока один: документация protobuf — это не отдельный артефакт, который кто-то обязан поддерживать руками, а производная схемы. Если описание метода живёт в вики, оно разъедется с кодом за месяц; если тот же текст лежит комментарием над rpc в .proto, из него собираются сразу четыре вещи: doc-комментарии в сгенерированном Go, markdown-страницы от protoc-gen-doc, descriptions в OpenAPI-контракте и ответы gRPC-рефлексии живому grpcurl-у. Один источник, четыре потребителя, ноль ручного переноса. Вторая тема урока — service-рефлексия: способность сервера рассказать о своих сервисах по запросу клиента. С ней grpcurl и buf curl работают против вашего API без единого локального файла — как swagger UI для RPC-мира. С ней же связан риск: рефлексия раскрывает поверхность API, и на проде её либо защищают перехватчиками из [тринадцатого урока](/courses/connect/lessons/13-bezopasnost/), либо выключают. Начнём с самого нижнего слоя — с комментариев в схеме — и поднимемся до OpenAPI-контракта для внешних потребителей.

### Proto-комментарии: единый источник правды

Комментарии в proto-файле — обычные C-стилевые строки: двойной слэш перед объявлением. Правило простое, как в Go: комментарий над объявлением service, rpc, message или поля становится документацией этого элемента во всех генератах. protoc-gen-go переносит его doc-комментарием на соответствующий тип и метод — комментарий над rpc попадает на интерфейс вашего хендлера и на клиентский метод, и IDE показывает его при каждом наведении. Комментарий над полем доезжает до struct-поля сгенерированного сообщения. Никакой магии: это текст, который protoc хранит в дескрипторах рядом с именами методов и полей, а плагины решают, что с ним делать. Отсюда первый рабочий вывод — комментарии пишутся для потребителя API, а не для автора: «GetNote возвращает заметку по идентификатору» бесполезно, когда имя метода и так это говорит; полезно объяснить семантику — что вернётся, если заметки нет, чьи заметки видны, какие идентификаторы валидны. Второй вывод — linter может держать планку: в buf есть отдельная категория правил COMMENTS (COMMENT_SERVICE, COMMENT_RPC, COMMENT_FIELD и соседние), которые требуют комментария у каждого сервиса, метода и поля; в дефолтный набор они не входят, их включают в buf.yaml вручную — и с тех пор PR без комментариев не проходит проверку. Третий вывод: формат комментариев стоит стандартизировать в команде — например, первая строка как краткое summary, дальше абзацы деталей, а пометки об экспериментальности и сроках удаления — отдельным тегом. Всё это обычный текст, но дисциплина в комментариях — это то, что отличает схему, по которой можно работать, от схемы, которую приходится раскапывать в исходниках хендлера.

### Схема, которая документирует себя сама

```go
// NoteService управляет заметками пользователя.
service NoteService {
  // GetNote возвращает заметку по идентификатору.
  // Заметка другого пользователя отвечает CodeNotFound:
  // существование чужих заметок не раскрываем.
  rpc GetNote(GetNoteRequest) returns (GetNoteResponse);

  // CreateNote создаёт заметку. Текст ограничен 10 000 символов
  // и проверяется на стороне сервера.
  rpc CreateNote(CreateNoteRequest) returns (CreateNoteResponse);
}

message GetNoteRequest {
  // Уникальный идентификатор заметки, например "n_01H...".
  string id = 1;
}
```

Комментарий над service описывает сервис целиком; комментарий над rpc — контракт метода, включая поведение с чужими заметками; комментарий над полем объясняет формат значения. Эти же строки позже окажутся в Go-коде, markdown-доках, OpenAPI и ответах рефлексии.

**Вопрос:** Где в Connect-проекте должен жить текст, описывающий контракт метода API?

- ✅ В proto-файле комментарием над rpc — оттуда его унаследуют доки, OpenAPI, Go-код и рефлексия
- В вики рядом с кодом: документации место в отдельной системе
- В сгенерированном Go-коде: правим комментарии прямо в gen/
- В OpenAPI-файле: он источник, из которого генерируют proto

Единственный источник правды — схема. Сгенерированный код из одиннадцатого урока перезаписывается при каждой генерации, вики и рукописный OpenAPI разъезжаются с кодом, а комментарий в proto доезжает во все потребители автоматически.

### Где вы увидите эти комментарии в Go-коде?

```go
// NoteService управляет заметками пользователя.
service NoteService {
  // GetNote возвращает заметку по идентификатору.
  rpc GetNote(GetNoteRequest) returns (GetNoteResponse);
}

// protoc-gen-go отработал по этой схеме и выдал gen/go/notes/v1/
```

**Что выведет программа?**

- Только в markdown от protoc-gen-doc — в Go-код комментарии не попадают
- ✅ Doc-комментарием над интерфейсом NoteServiceHandler и над методом GetNote хендлера и клиента
- В теле функций как строковые литералы рядом с методами
- Нигде: protoc выбрасывает комментарии при компиляции схемы

protoc хранит комментарии в дескрипторах, а protoc-gen-go переносит их doc-комментариями на сгенерированные типы и методы. Поэтому подпись метода в IDE вы читаете с описанием контракта — тем же текстом, что и в схеме.

### protoc-gen-doc: страницы документации из buf generate

Самый известный генератор документации protobuf — protoc-gen-doc: он читает дескрипторы схемы и её комментарии и выдаёт markdown, HTML, docbook или JSON. Настраивается как обычный плагин в buf.gen.yaml из одиннадцатого урока — либо локально установленным бинарником, либо удалённым плагином BSR buf.build/community/pseudomuto-doc, которому не нужен ничего в вашем окружении. Порядок работы не отличается от кодогенерации: buf generate читает схему, гоняет список плагинов, каждый пишет свои файлы в свой out-каталог. Доки едут в репозиторий рядом с кодом, и это не компромисс, а выигрыш: изменения документации ревьюятся в том же PR, что и изменения схемы, история — в git, а CI из одиннадцатого урока, который гоняет buf generate и проверяет git diff --exit-code, заодно не даёт докам отстать от схемы — забыл перегенерировать, тест не пройдёт. Дальше с артефактом можно делать что угодно: коммитить markdown в папку docs, публиковать HTML на внутренней странице, собирать из JSON собственный портал. Тот же проток документа помогает BSR-модулям из двенадцатого урока: в веб-интерфейсе Buf README модуля и описания элементов видны потребителям, а источник у них всё тот же — комментарии схемы. Единственная ловушка плагина — шаблоны: дефолтный markdown-шаблон выводит всё подряд, и на больших схемах документ превращается в простыню; protoc-gen-doc поддерживает кастомные шаблоны, где вы решаете, что попадёт в короткую справку, а что в полный справочник. Но это уже тюнинг — базовая связка buf generate плюс protoc-gen-doc закрывает потребность в доков для внутреннего API целиком.

### Дескрипторы: схема как данные

Под всеми генераторами лежит одна структура — FileDescriptorSet: компилированное представление proto-файлов, где описаны сообщения, поля, сервисы, методы и — важно для этой темы — комментарии с координатами строк. Получить её можно двумя командами, которые вы уже знаете: buf build -o image.binpb пишет буф-образ, который и есть тот же дескриптор-набор, а классический protoc --descriptor_set_out=out.binpb --include_imports собирает то же самое с импортами. Флаг --include_imports существен: без него в наборе не будет зависимостей вроде well-known-типов, и инструменты, которым нужен полный граф, споткнутся. Дальше начинается самое интересное: сгенерированный Go-код несёт дескриптор в себе. В каждом сгенерированном пакете лежит переменная вида File_notes_v1_note_service_proto — скомпилированный дескриптор файла, из которого всё сгенерировано. Через пакет google.golang.org/protobuf/reflect/protoreflect по этому дескриптору можно пройтись как по данным: перечислить сервисы, у сервиса методы, у метода вход и выход, у файла — комментарии. Именно поэтому рефлексия, генераторы и валидаторы работают без исходных proto-файлов: схема уже внутри вашего бинарника. Практические применения этого факта шире доков: динамический маршрутизатор, который регистрирует хендлеры по списку методов из дескриптора; служебный эндпоинт, отдающий JSON со списком версий API; проверка, что собранный сервис содержит те методы, о которых заявляет деплой. Buf breaking из десятого урока сравнивает дескрипторы старой и новой схемы — тот же механизм, поднятый на уровень CI. Так что когда слышите «схема — это данные», это не лозунг: FileDescriptorSet — обычный protobuf, который можно хранить, сравнивать, публиковать и читать из кода.

### Дескриптор в руки: CLI и protoreflect

```go
// CLI: собрать полный дескриптор-набор с импортами
buf build -o image.binpb
protoc -I proto --include_imports \
        --descriptor_set_out=out.binpb proto/notes/v1/notes.proto

// Go: дескриптор уже лежит в сгенерированном пакете
import "google.golang.org/protobuf/reflect/protoreflect"

func listMethods(file protoreflect.FileDescriptor) []string {
        names := []string{}
        services := file.Services()
        for i := 0; i < services.Len(); i++ {
                methods := services.Get(i).Methods()
                for j := 0; j < methods.Len(); j++ {
                        names = append(names, string(methods.Get(j).Name()))
                }
        }
        return names
}

// listMethods(notesv1.File_notes_v1_note_service_proto) -> [GetNote CreateNote ...]
```

buf build и protoc --descriptor_set_out дают схему как файл, сгенерированная переменная File_notes_v1_note_service_proto — как значение в памяти. protoreflect — стандартный способ читать сервисы и методы без исходников.

**Вопрос:** Что такое FileDescriptorSet и зачем он нужен, если proto-файлы и так лежат в репозитории?

- Это архив proto-файлов для бэкапа — практической пользы нет
- ✅ Скомпилированное представление схемы с сервисами, методами и комментариями: его читают генераторы, рефлексия и сравнение на breaking changes
- Кэш buf generate, ускоряющий повторную генерацию
- Конфигурация плагинов, которую protoc берёт из buf.gen.yaml

FileDescriptorSet — результат компиляции схемы: обычный protobuf с полным описанием API, включая комментарии с координатами. buf build -o и protoc --descriptor_set_out пишут его в файл; в сгенерированном Go он и так встроен переменной вида File_notes_v1_note_service_proto.

### Рефлексия: сервер рассказывает о себе

Service-рефлексия — протокольная возможность, унаследованная у gRPC: клиент спрашивает сервер «какие у тебя сервисы и методы», и сервер отвечает дескрипторами. Для классического gRPC за это отвечает пакет google.golang.org/grpc/reflection, а grpcurl без флагов -proto сначала стучится в рефлексию. В мире Connect ту же возможность даёт пакет connectrpc.com/grpcreflect: его рефлектор раздаёт дескрипторы ваших сервисов через обычные хендлеры, которые монтируются на тот же mux, что и бизнес-API, — и grpcurl, и buf curl начинают работать против вашего сервера без локальных proto-файлов. Пакет свежий, точное API сверяйте с README, но форма устойчивая: рефлектор конструируется со списком файлов, которые он готов отдавать, и монтируется двумя хендлерами — стабильной версией протокола и ранней alpha, которую слушают текущие grpcurl и buf curl. Список файлов — это те самые сгенерированные переменные File_..._proto из прошлого раздела: вы явно перечисляете, о чём сервер готов рассказывать. Зачем это нужно на практике. Первое — исследовать API: grpcurl list, grpcurl describe — и у новичка в команде вместо просьбы «скинь proto» есть живой источник с актуальным состоянием. Второе — отладка: buf curl против рефлексии отвечает на вопрос «а что сервер реально отдаёт в проде», включая версии методов, которые могли измениться после последнего деплоя. Третье — инструменты: универсальные клиенты и генераторы прокси могут строить запросы по ответу рефлексии. Цена вопроса — поверхность атаки: рефлексия раскрывает всё, что вы ей скормили, включая сервисы, которые вы считали внутренними. Поэтому правило тринадцатого урока продолжается и здесь: на публичном граничном сервере рефлексия либо выключена, либо закрыта авторизацией, либо отдаёт урезанный список файлов.

### Монтируем рефлексию рядом с бизнес-API

```go
import (
        "connectrpc.com/grpcreflect"
        notesv1 "yourorg/notes/gen/go/notes/v1"
)

func newMux() *http.ServeMux {
        mux := http.NewServeMux()

        // Бизнес-API: как в третьем уроке
        path, handler := notesv1connect.NewNoteServiceHandler(&notesServer{})
        mux.Handle(path, handler)

        // Рефлексия: grpcurl и buf curl видят сервисы без proto-файлов
        reflector := grpcreflect.NewReflector(
                grpcreflect.WithFiles(notesv1.File_notes_v1_note_service_proto),
        )
        mux.Handle(grpcreflect.NewHandlerV1(reflector))
        mux.Handle(grpcreflect.NewHandlerV1Alpha(reflector))

        return mux
}

// Проверка: grpcurl -plaintext localhost:8080 list
```

Рефлектору перечисляются файлы, о которых он рассказывает, — здесь только бизнес-схема, ничего лишнего. Два хендлера закрывают версии протокола рефлексии, которые слушают grpcurl и buf curl. Точное имя опций пакета сверьте с README — API ещё молод.

**Вопрос:** На прод-сервере смонтировали grpcreflect со списком всех файлов проекта, включая экспериментальные сервисы. Чем это плохо?

- Ничем: рефлексия только читает имена методов
- ✅ Рефлексия раскрывает полную поверхность API — включая то, что вы считали внутренним; на граничном сервере её защищают авторизацией или сужают список файлов
- Рефлексия замедляет бизнес-вызовы: каждый вызов ходит через неё
- grpcurl не умеет работать с Connect-серверами в принципе

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

### OpenAPI: контракт для внешнего мира

Proto-схема и рефлексия покрывают внутренний мир, где все понимают RPC. Но у внешнего мира свой язык: Swagger UI, шлюзы, генераторы SDK для языков, в которых proto-инструментария нет, и люди, которым «контракт — это открытый yaml, а не бинарник». Ответ на это — OpenAPI, и для proto-схем он тоже генерируется: главный генератор — protoc-gen-openapiv2 из grpc-gateway, который выдаёт OpenAPI 2.0 (Swagger) прямо из дескрипторов; есть и protoc-gen-openapi из gnostic с OpenAPI 3. Описания в контракте берутся из тех же комментариев схемы — у вас уже всё написано, генератор только разложит текст по полям summary и description. HTTP-маршруты описываются аннотациями google/api/annotations — это те самые googleapis-зависимости из двенадцатого урока: вы объявляете, что GetNote живёт на GET /notes/v1/{id}, и OpenAPI получает настоящий путь с параметрами, а не абстрактный rpc. Для Connect-проекта связка openapi с connect-сервисом — это мост к браузерным клиентам из девятого урока: фронтенд, который ходит через HTTP-шлюз или читает контракты в Swagger UI, получает привычный артефакт; а дружелюбие Connect-протокола к curl из четвёртого урока означает, что примеры из OpenAPI можно исполнять curl-ом буквально. Честная оговорка: protoc-gen-openapiv2 исторически описывает grpc-gateway-маршруты, а не connect-эндпоинты — для чисто connect-трафика путь выглядит как /notes.v1.NoteService/GetNote, и его в контракт дописывают шаблоном плагина или вручную. Поэтому практический порядок такой: OpenAPI — для потребителей за границей вашего RPC-мира (шлюзы, фронтенд-команды, публичный API), а proto и рефлексия — для тех, кто работает с API напрямую. Два артефакта из одного источника, и расхождение между ними физически невозможно.

### Полный buf.gen.yaml: код, доки, OpenAPI

```go
version: v2
plugins:
  - local: protoc-gen-go
    out: gen/go
    opt: paths=source_relative
  - local: protoc-gen-connect-go
    out: gen/go
    opt: paths=source_relative
  - remote: buf.build/community/pseudomuto-doc
    out: docs/
    opt: markdown,notes.md
  - local: protoc-gen-openapiv2
    out: openapi/
    opt: allow_merge=true,merge_file_name=notes
```

Первые два плагина — генерация Go-кода из одиннадцатого урока; третий пишет markdown-доки из комментариев; четвёртый собирает OpenAPI-контракт. Один buf generate обновляет всё: код, доки и контракт едут в один PR и не могут разъехаться.

### Инструменты документации и рефлексии

| Инструмент | Вход | Выход | Роль в проекте |
| --- | --- | --- | --- |
| комментарии в .proto | ваши руки | текст в дескрипторах | единственный источник правды для всего ниже |
| protoc-gen-doc | дескрипторы схемы | markdown, HTML, JSON | человекочитаемые доки на каждый PR |
| protoc-gen-openapiv2 | дескрипторы + аннотации | OpenAPI 2.0 | контракт для шлюзов, SDK и внешнего мира |
| buf build -o | модуль buf | образ (descriptor set) | схема как артефакт: хранить, сравнивать, публиковать |
| grpcreflect | сгенерированные File-переменные | эндпоинты рефлексии | сервер сам рассказывает о сервисах |
| grpcurl / buf curl | рефлексия или --schema | вызовы и describe | исследование и отладка живого API |

### Правила proto-комментариев, которые работают

- Пишите для потребителя: семантика, гарантии, ошибки — не пересказ имени метода
- Первая строка комментария — краткое summary, дальше абзацы деталей
- Описывайте ошибки: какой код и когда вернётся, — это читают и в IDE, и в OpenAPI
- Включите buf-категорию COMMENTS: PR без комментариев не проходит lint
- Экспериментальные методы помечайте тегом и сроком — и удаляйте вовремя
- Перегенерация доков — обязанность того же PR, что и изменение схемы; CI с git diff --exit-code следит

### Соберите buf.gen.yaml с доками и OpenAPI

Восстановите конфиг генерации: к двум плагинам Go-кода добавлены документационный и OpenAPI-плагины.

```go
version: v2
plugins:
  - local: protoc-gen-go
    out: gen/go
    opt: paths=source_relative
  - local: ___                       # хендлеры Connect
    out: gen/go
    opt: paths=source_relative
  - remote: buf.build/community/___  # markdown-доки из комментариев
    out: docs/
    opt: markdown,notes.md
  - local: ___                       # OpenAPI из тех же дескрипторов
    out: openapi/
    opt: allow_merge=true,merge_file_name=notes
```

Подсказка: Хендлеры даёт protoc-gen-connect-go; markdown-доки — удалённый плагин pseudomuto-doc из BSR; OpenAPI — protoc-gen-openapiv2 из grpc-gateway.

Правильные ответы в пропусках: `protoc-gen-connect-go`, `pseudomuto-doc`, `protoc-gen-openapiv2`

### Путь комментария: от rpc до потребителя

Расставьте шаги пути одного комментария над rpc — от написания до того, как его увидит внешний потребитель.

Правильный порядок строк:

1. `Автор пишет комментарий над rpc в notes.proto и открывает PR`
2. `buf lint с категорией COMMENTS проверяет, что комментарий есть`
3. `buf generate гоняет protoc-gen-go и protoc-gen-connect-go`
4. `Комментарий доезжает doc-комментарием на хендлер и клиента в Go`
5. `Тот же buf generate вызывает protoc-gen-doc и protoc-gen-openapiv2`
6. `Markdown-доки и OpenAPI коммитятся в тот же PR рядом с кодом`
7. `CI гоняет buf breaking: опубликованные ранее схемы не сломаны`
8. `Потребитель читает доки, Swagger UI или спросит рефлексию у живого сервера`

> **Не редактируйте генераты и не ведите доки руками.** Сгенерированный каталог — не место для правок: buf generate сотрёт их следующим запуском. Рукописная документация рядом с генерируемой — второй способ нажить расхождение: если что-то нельзя выразить комментарием схемы, это признак проблемы схемы, а не повод для отдельного документа. Исключение — README с инструкцией по сборке и запуску: он про процесс, а не про контракт.

> **Комментарий — часть контракта, а не украшение.** Проверяйте себя простым вопросом: может ли потребитель по одному тексту комментария написать корректный клиент? Если в комментарии нет поведения при отсутствии объекта, лимитов и кодов ошибок — контракт неполный. buf lint с правилами COMMENTS держит дисциплину наличия, а ревью — качества: на схему смотрите как на публичное API, потому что она им и является.

> **Как проходить этот курс.** Код урока — настоящий Go и настоящий yaml: buf.gen.yaml дописывается в проект из одиннадцатого урока, а рефлексия монтируется на хендлер из третьего. Браузерная песочница сайта buf и protoc-плагины не исполняет, поэтому исполняемых блоков в курсе нет — интерактивы работают на странице и дают XP, а живую проверку делайте grpcurl-ом против локального сервера.

### Карточки: документация и рефлексия

- **Единственный источник документации** — Комментарии в proto-файлах. Из них собираются Go-доккомментарии, markdown от protoc-gen-doc, descriptions в OpenAPI и ответы gRPC-рефлексии — всё одним buf generate.
- **protoc-gen-doc** — Плагин генерации документации: читает дескрипторы и комментарии, выдаёт markdown, HTML, JSON. В buf — локально или удалённым плагином buf.build/community/pseudomuto-doc.
- **FileDescriptorSet** — Скомпилированная схема: сервисы, методы, поля и комментарии. buf build -o пишет её файлом; protoc --descriptor_set_out --include_imports — то же с импортами. В Go встроена в сгенерированный пакет.
- **grpcreflect** — Пакет connectrpc.com/grpcreflect: рефлектор с явным списком File-файлов монтируется хендлерами V1 и V1Alpha на общий mux — grpcurl и buf curl работают без proto-файлов.
- **OpenAPI для Connect** — protoc-gen-openapiv2 генерирует контракт из схемы; маршруты описываются google/api/annotations. Назначение — внешний мир: шлюзы, Swagger UI, генераторы SDK. Proto остаётся источником.
- **Рефлексия и безопасность** — Рефлексия раскрывает всю схему перечисленных файлов, включая внутренние сервисы. На граничном сервере — выключить, закрыть авторизацией или отдавать сокращённый список файлов.

### Что дальше по курсу

Документация замкнулась на buf-конвейер: схема описывает контракт словами, генераторы разносят его по потребителям, рефлексия отвечает на вопросы живого сервера. Дальше по курсу: шестнадцатый урок — тестирование, где эта же схема станет предметом тестов полного стека на httptest — [шестнадцатый](/courses/connect/lessons/16-testirovanie/); метрики семнадцатого покажут, как документированный API ведёт себя под нагрузкой. Для повторения: конвейер генерации — в [одиннадцатом уроке](/courses/connect/lessons/11-buf-generate/), lint и breaking changes — в [десятом](/courses/connect/lessons/10-buf-lint/), зависимость googleapis — в [двенадцатом](/courses/connect/lessons/12-bsr/). Программа всех двадцати пяти уроков — на [странице курса](/courses/connect/).

## Коротко: что запомнить

- Документация protobuf-схемы — производная, а не отдельный труд: единственный источник — комментарии в .proto, всё остальное генерируется.
- Комментарий над rpc доезжает в четыре места: doc-комментарий Go, markdown от protoc-gen-doc, description в OpenAPI, ответы рефлексии.
- buf lint умеет требовать комментарии: категория правил COMMENTS включается в конфиге, PR без документации не проходит.
- FileDescriptorSet — схема как данные: buf build -o и protoc --descriptor_set_out дают файл, сгенерированный пакет несёт дескриптор в себе; protoreflect читает его из кода.
- connectrpc.com/grpcreflect поднимает gRPC-рефлексию на том же mux: grpcurl и buf curl работают без proto-файлов; на проде рефлексию защищают или сужают список файлов.
- OpenAPI генерируется из тех же дескрипторов (protoc-gen-openapiv2, OpenAPI 3 — через gnostic): контракт для шлюзов, Swagger UI и внешних SDK, маршруты — через google/api/annotations.
- Один buf.gen.yaml собирает код, доки и контракт одним запуском: расхождение артефактов физически невозможно, CI с git diff --exit-code держит их свежими.

## Домашнее задание

Проведите аудит документации своего notes-сервиса и достройте конвейер. Шаг первый: включите в buf.yaml категорию правил COMMENTS, запустите buf lint и допишите комментарии ко всем сервисам, методам и полям, на которые он пожалуется, — для каждого метода опишите поведение при отсутствии объекта и возвращаемые коды ошибок. Шаг второй: добавьте в buf.gen.yaml плагины pseudomuto-doc и protoc-gen-openapiv2, перегенерируйте и убедитесь, что в docs/ появился markdown с вашим текстом, а в openapi/ — контракт; закоммитьте всё одним PR. Шаг третий: смонтируйте grpcreflect рядом с бизнес-хендлером, запустите grpcurl list и grpcurl describe против локального сервера, затем вызовите метод через buf curl без --schema — рефлексия должна дать схему. Шаг четвёртый: попробуйте откомментировать экспериментальный сервис из списка рефлексии и решите, что на публичном контуре вы с ним делаете — закрываете перехватчиком из тринадцатого урока или убираете из WithFiles. Запишите в заметки три строки «артефакт — генератор — потребитель»: она станет чеклистом документации в следующем проекте.

## Вопросы и ответы по уроку

### Зачем OpenAPI, если уже есть proto и рефлексия?

Потому что внешний мир читает OpenAPI: Swagger UI, HTTP-шлюзы и генераторы SDK для языков без proto-инструментария живут в этом формате. Генерация из тех же дескрипторов даёт два артефакта из одного источника — для RPC-потребителей остаётся proto, для HTTP-мира OpenAPI, и расхождение невозможно.

### protoc-gen-openapiv2 генерирует пути grpc-gateway. А connect-эндпоинты?

Исторически плагин описывает gateway-маршруты из аннотаций; чистый connect-путь вида /notes.v1.NoteService/GetNote в контракт дописывают шаблоном плагина или постобработкой. На практике этого достаточно: контракт нужен внешним потребителям за шлюзом, а прямые RPC-клиенты работают по proto и рефлексии.

### Как не дать документации устареть?

Убрать возможность устареть: комментарии живут в схеме, доки генерируются тем же buf generate, что и код, и коммитятся тем же PR. CI из одиннадцатого урока с buf generate и git diff --exit-code не пропустит изменение схемы без перегенерации — доки отстать не могут.
