Разработка gRPC API для веб-приложения
gRPC — RPC-фреймворк от Google на основе Protocol Buffers и HTTP/2. Обеспечивает строгую типизацию через .proto-файлы, двунаправленный стриминг и значительно меньший overhead по сравнению с JSON. Наиболее уместен для межсервисного взаимодействия (microservices) и мобильных клиентов с ограниченным каналом.
Protocol Buffers
Контракт сервиса определяется в .proto файлах:
syntax = "proto3";
package articles.v1;
import "google/protobuf/timestamp.proto";
message Article {
string id = 1;
string title = 2;
string body = 3;
string author_id = 4;
repeated string tag_ids = 5;
google.protobuf.Timestamp created_at = 6;
}
message GetArticleRequest { string id = 1; }
message ListArticlesRequest {
int32 page = 1;
int32 limit = 2;
string status = 3;
}
message ListArticlesResponse {
repeated Article articles = 1;
int32 total = 2;
}
service ArticleService {
rpc GetArticle(GetArticleRequest) returns (Article);
rpc ListArticles(ListArticlesRequest) returns (ListArticlesResponse);
rpc CreateArticle(CreateArticleRequest) returns (Article);
rpc WatchArticle(GetArticleRequest) returns (stream Article); // серверный стриминг
}
Из .proto генерируется код для любого языка: protoc --go_out=. --go-grpc_out=..
Реализация сервера (Go)
type ArticleServer struct {
pb.UnimplementedArticleServiceServer
db *sql.DB
}
func (s *ArticleServer) GetArticle(ctx context.Context, req *pb.GetArticleRequest) (*pb.Article, error) {
row := s.db.QueryRowContext(ctx, "SELECT id, title, body FROM articles WHERE id = $1", req.Id)
var a pb.Article
if err := row.Scan(&a.Id, &a.Title, &a.Body); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, status.Error(codes.NotFound, "article not found")
}
return nil, status.Error(codes.Internal, err.Error())
}
return &a, nil
}
// Запуск сервера
lis, _ := net.Listen("tcp", ":50051")
grpcServer := grpc.NewServer(grpc.UnaryInterceptor(authInterceptor))
pb.RegisterArticleServiceServer(grpcServer, &ArticleServer{db: db})
grpcServer.Serve(lis)
Стриминг
gRPC поддерживает 4 типа взаимодействия:
// Unary (обычный запрос/ответ)
rpc GetArticle(Request) returns (Response);
// Server streaming (один запрос → поток ответов)
rpc WatchUpdates(Request) returns (stream Event);
// Client streaming (поток запросов → один ответ)
rpc UploadChunks(stream Chunk) returns (UploadResult);
// Bidirectional streaming
rpc Chat(stream Message) returns (stream Message);
Серверный стриминг удобен для: real-time уведомлений, экспорта большого объёма данных, live-результатов.
Перехватчики (Interceptors)
func authInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.Unauthenticated, "missing metadata")
}
token := md.Get("authorization")
if !validateToken(token[0]) {
return nil, status.Error(codes.Unauthenticated, "invalid token")
}
return handler(ctx, req)
}
Interceptors — аналог middleware: логирование, auth, трассировка (OpenTelemetry), rate limiting.
gRPC в браузере
gRPC не работает напрямую в браузере (HTTP/2 binary framing недоступен). Решения:
- gRPC-Web — специальный протокол с Envoy proxy на стороне сервера
- Connect (Buf) — современная альтернатива, работает с HTTP/1.1 и HTTP/2, совместима с gRPC
# Buf CLI для генерации кода
buf generate --template buf.gen.yaml
Документация и тестирование
-
gRPCurl — curl для gRPC:
grpcurl -plaintext localhost:50051 articles.v1.ArticleService/GetArticle - Postman — поддерживает gRPC с версии 9.7
- Buf Schema Registry — централизованное хранение .proto файлов
Когда выбирать gRPC
gRPC оправдан при:
- Межсервисное взаимодействие внутри инфраструктуры
- Строгий контракт между командами
- Нужен эффективный бинарный протокол (IoT, mobile)
- Двунаправленный стриминг
REST/GraphQL лучше: публичный API, браузерные клиенты без grpc-web, быстрый прототип.
Сроки
gRPC-сервис (5–10 методов, auth interceptor, proto-контракт): 1–2 недели. С bidirectional streaming, service mesh (Istio/Linkerd), gRPC-Web для браузера: 2–4 недели.







