Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 3 из 25 ~20 мин +50 XP

Установка и кодогенерация protoc

.proto-файл сам по себе ничего не исполняет — превращает его в Go-код компилятор protoc с двумя плагинами: protoc-gen-go и protoc-gen-go-grpc. В этом уроке поставим весь набор, разберём команду кодогенерации по флагам и заглянем в сгенерированные файлы .pb.go, чтобы дальше читать их как обычный код проекта.

Содержание урока · 9 разделов

Схема из прошлого урока лежит в api/greeter/v1/greeter.proto — и сейчас это просто текст. Чтобы из него получились типы Go, интерфейс сервера и клиентская заглушка, нужен компилятор protoc и два плагина. Настройка делается один раз на машину разработчика и один раз в CI, но именно на этом шаге новичок чаще всего застревает: плагин не найден, файлы легли не в ту папку, версия runtime не совпала с версией компилятора. Поэтому пройдём весь путь по шагам: установка protoc под вашу ОС, установка плагинов через go install, команда генерации с разбором каждого флага, анатомия двух сгенерированных файлов и четыре самые частые ошибки с их причинами. После этого урока фраза «перегенерируй pb» перестанет быть магией — вы будете знать, что именно происходит между .proto и компиляцией.

Шаг 1: установить protoc и плагины#

  1. macOS: brew install protobuf
  2. Debian/Ubuntu: apt install -y protobuf-compiler — учтите, в старых дистрибутивах в репозитории лежит устаревшая версия; свежее — бинарный zip с GitHub-страницы релизов protobuf
  3. Windows: choco install protoc, либо тот же zip protoc-<версия>-win64.zip из релизов — распаковать и добавить в PATH
  4. Проверка: protoc --version печатает строку вида libprotoc 25.x (число у вас будет другое)
  5. Плагин сериализации: go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
  6. Плагин gRPC: go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
  7. Проверка: оба бинарника должны быть видны в PATH — они уходят в $GOPATH/bin (или $GOBIN); protoc вызывает их как внешние программы по имени

Команда protoc: разбор по флагам#

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

Рабочая команда генерации

GO
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

--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, после чего маркер из интерфейса исчезает. Моя позиция: не отключайте. Незаметная ошибка компиляции, которая выявляет пропущенный метод на билде, стоит одной строки встраивания — а потомок, который будет расширять схему, скажет вам спасибо.

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

GO
package 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 только один раз за всю жизнь проекта.

Проверка: файлы кодогенерации

Какие 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

Восстановите команду генерации: имя компилятора, папки вывода и ключевое слово опции путей.

--proto_path=api \ --=gen \ --go_opt=paths= \ --go-grpc_out= \ api/greeter/v1/greeter.proto

Пайплайн: от установки до реализации

Расставьте шаги в правильном порядке — от чистой машины до компилирующегося сервера.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

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

Инструменты на месте, код сгенерирован — пора писать руками. Следующий урок собирает первый 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 падает. Возвратите флаг на место: заодно поймёте, что именно даёт каждый плагин.

Личные заметкиПусто
Хранятся только в вашем браузере (gRPC · Установка и кодогенерация protoc)

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

Нужен ли 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 не найдёт плагин.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Сравните с альтернативами

Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.

Следующая ступень учебного пути

Connect25 уроков~12 чпродвинутый25 уроков уже доступны

Connect: современный RPC от Buf

Курс Connect (Buf): протоколы, клиенты, ошибки, interceptors, браузерные клиенты, buf CLI и BSR. 25 уроков и итоговый проект API.

  • Строить сервисы на connectrpc.com/connect
  • Понимать три протокола и выбирать правильный
  • Настраивать buf: lint, breaking changes, generate
  • Писать interceptors и обрабатывать ошибки