gRPC в Go
gRPC — RPC-фреймворк от Google поверх HTTP/2 и Protocol Buffers. Контракт описывают в .proto, код генерируют protoc и плагины Go. Статья дополняет обзор REST, GraphQL и gRPC и справочник по gRPC.
См. также: Веб на stdlib · Gin · Дженерики · Асинхронность.
gRPC и REST — когда что выбирать
| Критерий | gRPC | REST (JSON) |
|---|---|---|
| Формат | Бинарный Protobuf | Текст JSON |
| Транспорт | HTTP/2, multiplexing | HTTP/1.1 или HTTP/2 |
| Контракт | .proto, строгая схема | OpenAPI, часто «по факту» |
| Браузер | Нужен grpc-web / proxy | Нативно |
| Стриминг | Unary, server/client/bidi stream | SSE, WebSocket отдельно |
| Типичное применение | Микросервис ↔ микросервис | Публичное API, BFF |
В Go один процесс часто поднимает REST для внешних клиентов и gRPC для внутренних вызовов — см. пример микросервиса.
Инструменты
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
Разбор:
protoc(компилятор protobuf) ставят отдельно с protobuf.dev.protoc-gen-goгенерирует Go-структуры сообщений.protoc-gen-go-grpcгенерирует интерфейсы сервиса и регистрацию в gRPC.
В go.mod обычно появляются google.golang.org/grpc и google.golang.org/protobuf.
Файл .proto
Минимальный контракт телефонной книги (упрощённый учебный пример):
syntax = "proto3";
package phonebook.v1;
option go_package = "example.com/phonebook/pb;pb";
message Contact {
string name = 1;
string phone = 2;
}
message GetRequest {
string name = 1;
}
message GetResponse {
Contact contact = 1;
}
service PhoneBook {
rpc Get(GetRequest) returns (GetResponse);
}
Разбор:
proto3— актуальная версия синтаксиса; поля нумеруются и не меняют номер после релиза.go_packageзадаёт import path и имя Go-пакета (pb).service PhoneBookобъявляет RPC-методGetс типами запроса и ответа.
Генерация:
protoc --go_out=. --go-grpc_out=. \
--go_opt=paths=source_relative \
--go-grpc_opt=paths=source_relative \
phonebook/v1/phonebook.proto
Разбор:
paths=source_relativeкладёт.pb.goрядом с.proto— удобно в монорепо.- В репозитории часто коммитят сгенерированный код или генерируют в CI через
go:generate.
Сервер
type server struct {
pb.UnimplementedPhoneBookServer
db map[string]string
}
func (s *server) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse, error) {
phone, ok := s.db[req.GetName()]
if !ok {
return nil, status.Errorf(codes.NotFound, "contact %q", req.GetName())
}
return &pb.GetResponse{
Contact: &pb.Contact{Name: req.GetName(), Phone: phone},
}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatal(err)
}
s := grpc.NewServer()
pb.RegisterPhoneBookServer(s, &server{db: map[string]string{"Ann": "+1-555-0100"}})
log.Fatal(s.Serve(lis))
}
Разбор:
- Встраивание
UnimplementedPhoneBookServer— forward compatibility: новые методы в.protoне ломают старый бинарник до реализации. status.Errorf(codes.NotFound, ...)переводит доменную ошибку в gRPC-код; клиент читаетstatus.Code(err).- Порт
:50051— распространённый convention для gRPC (не стандарт, а привычка).
Клиент
conn, err := grpc.Dial("localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
}
defer conn.Close()
client := pb.NewPhoneBookClient(conn)
resp, err := client.Get(context.Background(), &pb.GetRequest{Name: "Ann"})
if err != nil {
log.Fatal(err)
}
fmt.Println(resp.GetContact().GetPhone())
Разбор:
grpc.Dialсоздаёт долгоживущее HTTP/2-соединение; клиент переиспользует его для многих RPC.insecure.NewCredentials()— только для локальной разработки; в проде — TLS (credentials.NewClientTLSFromFileили mTLS).- Первый аргумент RPC — всегда
context.Contextдля таймаутов и отмены — см. асинхронность.
В production включайте TLS или mTLS между сервисами. Незашифрованный gRPC допустим лишь в доверенной сети или на localhost.
Streaming (кратко)
gRPC поддерживает четыре вида вызовов:
| Вид | Направление |
|---|---|
| Unary | один запрос — один ответ |
| Server streaming | клиент шлёт один запрос, сервер — поток ответов |
| Client streaming | клиент шлёт поток, сервер — один ответ |
| Bidirectional | оба потока одновременно |
В .proto это задаёт ключевое слово stream у аргумента или возвращаемого типа. На сервере реализуют Send/Recv на stream-объекте; на клиенте — аналогично. Для больших выборок из БД server streaming снижает задержку первого байта по сравнению с одним огромным JSON.
Ошибки и метаданные
- Коды:
codes.NotFound,InvalidArgument,DeadlineExceeded,Unavailable. - Проверка на клиенте:
st, ok := status.FromError(err)
if ok && st.Code() == codes.NotFound {
// ...
}
- Metadata — аналог HTTP-заголовков (trace-id, auth token); передаётся через
metadata.NewOutgoingContext.
Тестирование
- Unit: мок интерфейса
PhoneBookServerили тестовый in-memory сервер. - Интegration:
bufconn/ локальный listener без сети:
lis := bufconn.Listen(1024 * 1024)
// Register server on lis, dial через grpc.WithContextDialer
- gRPC совместим с table-driven тестами; для HTTP-gateway поверх gRPC используют grpc-gateway (отдельная тема).
Связка с REST
Паттерн gRPC + grpc-gateway: один .proto, gRPC внутри кластера, JSON REST снаружи. Альтернатива — два контракта (OpenAPI + proto) с риском рассинхронизации.
Типичный путь сервиса контактов: CLI → веб на stdlib → REST на Gin → gRPC (эта статья).
Ключевые тезисы
- Контракт в
.proto— источник правды для сообщений и RPC. - Сервер и клиент генерируются; ручной JSON не нужен между Go-сервисами.
context, кодыstatusи TLS — обязательная часть production-разработки.- Для публичного API чаще оставляют REST; gRPC — для низкой задержки и строгих контрактов между сервисами.
Мини-практикум
- Опишите
.protoс методамиList(server stream) иAdd(unary). - Поднимите сервер и клиент на localhost; добавьте
context.WithTimeoutна вызов. - Сравните размер payload Protobuf и JSON для одной структуры
Contact.
Типичные ошибки
| Ошибка | Что делать |
|---|---|
Менять номера полей в .proto | Только добавлять поля с новыми номерами |
Забыть Unimplemented*Server | Встроить заглушку из generated-кода |
grpc.Dial без TLS в проде | Настроить credentials |
Игнорировать ctx на сервере | Проверять ctx.Done() в долгих операциях |
Связанные материалы
- Справочник по gRPC
- REST, GraphQL и gRPC — обзор
- Следующий шаг — профилирование и fuzz
Базовый разбор HTTP и HTTPS — HTTP как основа веб-интеграций.
См. также
Другие статьи этого же раздела в боковом меню (как на странице "О разделе"). Основы языка Go - философия простоты, модель компиляции и идиоматичный подход к системной разработке. Go — это статически типизированный язык программирования общего назначения, разработанный компанией Google для создания эффективных, масштабируемых и надежных систем. Набор советов, правил, принципов и обычаев в разработке на этом языке. История Go - инженерные цели языка, философия простоты и эволюция инструментов экосистемы. Экосистема приложений на Go - встроенные инструменты, workflow разработки и практики сопровождения проектов. Кавычки, rune и string, точка, запятая, автоматическая вставка точки с запятой, скобки, подчёркивания и типичные ошибки новичков в Go. Предопределённые идентификаторы не являются ключевыми словами, но имеют специальное значение в языке. Их можно переопределить в локальной области видимости, но делать это не рекомендуется. Набор функций, которые включены в стандартную библиотеку языка. Особенности Go - интерфейсы, композиция, модель ошибок и практики написания поддерживаемого кода. Go вводит конкурентность через встроенные синтаксические конструкции и правила выполнения. Ниже рассматриваются основные направления практического применения Go, объяснённые через призму его технических характеристик и требований реальных инфраструктур. Типизация, набор правил определения типа данных значений языка.Основы языка Go
Что требуется знать перед началом изучения языка программирования Go
Рекомендации по разработке на Go
История языка Go
Экосистема приложений на Go
Синтаксис и пунктуация в Go
Ключевые слова языка Go
Встроенные функции и пакеты Go
Особенности языка Go
Синтаксические конструкции Go
Области применения Go
Типы данных и объявление переменных в Go