Розробка 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 тижні.







