Содержание урока · 9 разделов
Схема из прошлого урока лежит в api/greeter/v1/greeter.proto — и сейчас это просто текст. Чтобы из него получились типы Go, интерфейс сервера и клиентская заглушка, нужен компилятор protoc и два плагина. Настройка делается один раз на машину разработчика и один раз в CI, но именно на этом шаге новичок чаще всего застревает: плагин не найден, файлы легли не в ту папку, версия runtime не совпала с версией компилятора. Поэтому пройдём весь путь по шагам: установка protoc под вашу ОС, установка плагинов через go install, команда генерации с разбором каждого флага, анатомия двух сгенерированных файлов и четыре самые частые ошибки с их причинами. После этого урока фраза «перегенерируй pb» перестанет быть магией — вы будете знать, что именно происходит между .proto и компиляцией.
Шаг 1: установить protoc и плагины#
- macOS: brew install protobuf
- Debian/Ubuntu: apt install -y protobuf-compiler — учтите, в старых дистрибутивах в репозитории лежит устаревшая версия; свежее — бинарный zip с GitHub-страницы релизов protobuf
- Windows: choco install protoc, либо тот же zip protoc-<версия>-win64.zip из релизов — распаковать и добавить в PATH
- Проверка: protoc --version печатает строку вида libprotoc 25.x (число у вас будет другое)
- Плагин сериализации: go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
- Плагин gRPC: go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
- Проверка: оба бинарника должны быть видны в PATH — они уходят в $GOPATH/bin (или $GOBIN); protoc вызывает их как внешние программы по имени
Плагины развиваются вместе с runtime, и свежий protoc при старом runtime иногда рождает код, который старая библиотека не переварит. Для локальной разработки берите @latest, для CI фиксируйте конкретную версию — @v1.31.0 у protoc-gen-go, @v1.3.0 у protoc-gen-go-grpc — и обновляйте осознанно.
Команда protoc: разбор по флагам#
Сама генерация — одна команда, и её стоит не копировать вслепую, а разобрать. protoc ничего не знает про Go: он читает схему, а генерацию делегирует плагинам, вызывая их как дочерние процессы по имени. За то, куда попадут файлы и как устроятся пути, отвечают отдельные флаги — и именно вокруг них возникают почти все вопросы новичков. Команда ниже — рабочий минимум для схемы из двух предыдущих уроков.
Рабочая команда генерации
GOprotoc \
--proto_path=api \
--go_out=gen \
--go_opt=paths=source_relative \
--go-grpc_out=gen \
--go-grpc_opt=paths=source_relative \
api/greeter/v1/greeter.proto--proto_path — корень, относительно которого protoc ищет импортируемые схемы; аргумент greeter.proto задаётся именно относительно него. --go_out=gen — папка для кода сериализации, --go-grpc_out=gen — для gRPC-клиента и сервера. paths=source_relative сохраняет структуру папок относительно proto_path, поэтому файлы лягут в gen/greeter/v1/. Опции нужно продублировать каждому плагину отдельно — один общий --go_opt на оба плагина не распространяется.
Что генерируется: два файла, две ответственности#
После команды в gen/greeter/v1/ появятся два файла, и их разделение — не случайность. greeter.pb.go создаёт protoc-gen-go: структуры HelloRequest и HelloReply с полями из схемы, геттеры Get-методы (они безопасно возвращают нулевые значения даже для nil-структуры), Marshal/Unmarshal и весь низкоуровневый код поверх runtime-пакета google.golang.org/protobuf. greeter_grpc.pb.go создаёт protoc-gen-go-grpc: интерфейс GreeterServer для вашей реализации, структуру GreeterClient с готовыми методами-вызовами и функцию RegisterGreeterServer. Разделение имеет смысл: сериализация и транспорт развиваются независимо, и protobuf-код нужен даже там, где gRPC нет. Оба файла начинаются с пометки Code generated ... DO NOT EDIT — protoc перезапишет любую правку при следующем запуске, поэтому в код-ревью их диффы просматривают, но не редактируют. Старый импорт github.com/golang/protobuf — устаревший фасад, в новых проектах берите google.golang.org/protobuf, и запомните этот путь: именно его добавит в go.mod команда go mod tidy после первой генерации.
Заглянем внутрь greeter_grpc.pb.go
GO// Фрагмент сгенерированного файла (упрощён для чтения).
type GreeterServer interface {
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
mustEmbedUnimplementedGreeterServer()
}
type UnimplementedGreeterServer struct{}
func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {}
func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) {
s.RegisterService(&Greeter_ServiceDesc, srv)
}Интерфейс сервера — это ваши rpc-методы из .proto, переведённые в Go-сигнатуры, плюс обязательный метод-маркер mustEmbedUnimplementedGreeterServer. RegisterGreeterServer связывает реализацию с дескриптором сервиса — именно её вы вызывали в main() первого урока. Клиентская сторона того же файла даёт GreeterClient с готовым SayHello.
Unimplemented-паттерн: зачем навязывают встраивание#
Строка mustEmbedUnimplementedGreeterServer выглядит как чужеродный хак, но решение осмысленное. Если в схему добавят метод WatchOrders, интерфейс GreeterServer пополнится — и все существующие реализации перестанут компилироваться. Встраивание UnimplementedGreeterServer спасает: у него есть «заглушки» на все методы, возвращающие статус Unimplemented, поэтому ваш server продолжает собираться, а новый метод просто вежливо отвечает «не поддерживаю», пока вы его не реализуете. Требование появилось в генераторе в 2020 году как дефолт; несогласным дают выход — опцию --go-grpc_opt=require_unimplemented_servers=false, после чего маркер из интерфейса исчезает. Моя позиция: не отключайте. Незаметная ошибка компиляции, которая выявляет пропущенный метод на билде, стоит одной строки встраивания — а потомок, который будет расширять схему, скажет вам спасибо.
«protoc-gen-go: program not found» — плагин не в PATH, проверьте $GOPATH/bin. Файлы легли не туда — вы забыли paths=source_relative, и protoc разложил код по папкам из go_package. «file ... is newer than the code» — компилятор свежее runtime-библиотеки: обновите google.golang.org/protobuf или возьмите protoc постарше. «unknown service» при вызове — сервер запущен, но RegisterGreeterServer забыли вызвать.
paths=source_relative и структура gen/#
Опция paths управляет тем, как protoc разложит файлы по папкам вывода. Без неё он строит дерево из go_package: запросите --go_out=gen при go_package = "github.com/example/greeter/gen/greeterv1", и файлы окажутся в gen/github.com/example/greeter/gen/greeterv1/ — гнездо, которое никто не просил. С paths=source_relative файл кладётся рядом с исходником относительно proto_path: api/greeter/v1/greeter.proto даёт gen/greeter/v1/greeter.pb.go. Два расклада считаются рабочими: держать схемы в api/ или proto/, а генерат — в gen/ (импорт-пути чистые, читателю очевидно, что код генерированный), либо класть .proto рядом с пакетом. Главное — go_package должен указывать на тот путь, по которому пакет реально импортируется, иначе go mod tidy не соберёт картинку. Менять расклад после того, как схема опубликована, — боль, выберите один раз и запишите в Makefile.
Автоматизация: go:generate
GOpackage gen
// Перегенерация после правок схем: go generate ./...
//
//go:generate protoc --proto_path=api --go_out=gen --go_opt=paths=source_relative --go-grpc_out=gen --go-grpc_opt=paths=source_relative api/greeter/v1/greeter.proto
//go:generate go mod tidyДиректива //go:generate хранит команду генерации в коде проекта, и go generate ./... запускает её для всех пакетов. Один источник правды: схема в api/, команда — в этой директиве, руки вводят protoc только один раз за всю жизнь проекта.
В индустрии популярен buf: единый инструмент вместо связки protoc+плагины, с линтером схем и проверкой ломающих изменений. Для обучения я всё же начинаю с protoc — он показывает механику процесса, а buf станет естественным апгрейдом, который разбирается в курсе про Connect.
Проверка: файлы кодогенерации
Какие Go-файлы создаёт связка protoc + плагины из greeter.proto?
Проверка: Unimplemented-паттерн
Зачем сгенерированный интерфейс требует встраивать UnimplementedGreeterServer?
Что получится без --go-grpc_out?
Прочитайте код и выберите, что он выведет:
protoc --proto_path=api \
--go_out=gen \
--go_opt=paths=source_relative \
api/greeter/v1/greeter.protoСоберите команду protoc
Восстановите команду генерации: имя компилятора, папки вывода и ключевое слово опции путей.
Пайплайн: от установки до реализации
Расставьте шаги в правильном порядке — от чистой машины до компилирующегося сервера.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Что дальше по курсу#
Инструменты на месте, код сгенерирован — пора писать руками. Следующий урок собирает первый gRPC-сервер целиком: структура проекта, реализация интерфейса, graceful shutdown. За ним — клиент с grpc.NewClient и первый сквозной вызов. А когда захочется вылизать процесс сборки, вернитесь к теме: в уроке про тестирование сгенерированный код тестируют через bufconn, без реального порта и сети.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Потребуются три инструмента: protoc + плагины protoc-gen-go и protoc-gen-go-grpc, оба ставятся через go install и должны быть в PATH.
- protoc делегирует генерацию плагинам: --go_out — сериализация, --go-grpc_out — транспорт; опции paths дублируются каждому плагину.
- paths=source_relative сохраняет структуру папок; без него protoc строит дерево из go_package.
- Генерятся два файла: greeter.pb.go (типы, геттеры, маршалинг) и greeter_grpc.pb.go (интерфейс сервера, клиент, Register-функция).
- Unimplemented-паттерн — прямая совместимость: новые методы схемы не ломают старые серверы; отключать не советую.
- В CI фиксируйте версии плагинов, а не @latest — расхождение версий protoc и runtime ловится ошибкой «is newer than the code».
- Команду генерации держите в //go:generate или Makefile — один источник правды на проект.
Домашнее задание
Соберите полный конвейер для схемы заказов из урока 02: положите order.proto в api/shop/v1/, поставьте protoc и плагины, сгенерируйте код в gen/shopv1/ командой с paths=source_relative и выполните go mod tidy. Затем умышленно уберите --go-grpc_out и запустите снова — убедитесь, что файл _grpc.pb.go пропал и компиляция с RegisterXServer падает. Возвратите флаг на место: заодно поймёте, что именно даёт каждый плагин.
Личные заметкиПусто
Вопросы и ответы по уроку
Нужен ли Go для установки protoc?
Сам protoc — отдельный бинарник (brew, apt, choco, zip). А вот плагины protoc-gen-go и protoc-gen-go-grpc — Go-программы, они ставятся через go install, поэтому Go нужен.
Файлы .pb.go можно править руками?
Нет: protoc перезапишет изменения при следующей генерации. В первой строке каждого файла об этом так и написано. Править надо схему .proto и код, который использует сгенерированные типы.
Чем protoc отличается от protoc-gen-go?
protoc — компилятор схем, он парсит .proto и раздаёт работу плагинам. protoc-gen-go — плагин, который переводит сообщения в Go-код сериализации; protoc-gen-go-grpc — второй плагин для транспортного слоя gRPC.
Как поставить protoc и protoc-gen-go на Windows?
protoc скачивается бинарным zip с релизов protobuf на GitHub (выберите win64, распакуйте и добавьте bin в PATH) либо ставится пакетным менеджером: choco install protoc или winget. Плагины ставятся одинаково на любой ОС: go install google.golang.org/protobuf/cmd/protoc-gen-go@latest и go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest — главное, чтобы %GOPATH%/bin (обычно %USERPROFILE%\go\bin) тоже был в PATH, иначе protoc не найдёт плагин.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Сравните с альтернативами
Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.
Изучайте дальше
Следующая ступень учебного пути
Connect: современный RPC от Buf
Курс Connect (Buf): протоколы, клиенты, ошибки, interceptors, браузерные клиенты, buf CLI и BSR. 25 уроков и итоговый проект API.
- Строить сервисы на connectrpc.com/connect
- Понимать три протокола и выбирать правильный
- Настраивать buf: lint, breaking changes, generate
- Писать interceptors и обрабатывать ошибки