gRPC-GoJuniorCoding

Как реализовать gRPC-клиент на Go?

gRPC-клиент в Go создаётся через grpc.NewClient(), которому передаётся адрес и опции соединения. Затем из соединения создаётся сгенерированный stub и вызываются методы через context.

Пошаговая реализация gRPC-клиента на Go

Шаг 1 — .proto и кодогенерация

syntax = "proto3";
package user;
option go_package = "example.com/gen/user";

service UserService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
  rpc ListUsers(ListUsersRequest) returns (stream User);
}
message GetUserRequest  { string id = 1; }
message GetUserResponse { User user = 1; }
message ListUsersRequest{}
message User { string id = 1; string name = 2; }
protoc --go_out=. --go-grpc_out=. user.proto

Шаг 2 — установка зависимостей

go get google.golang.org/grpc@latest
go get google.golang.org/protobuf@latest

Шаг 3 — код клиента

package main

import (
	"context"
	"io"
	"log"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	pb "example.com/gen/user"
)

func main() {
	// NewClient не открывает соединение сразу — оно lazy
	conn, err := grpc.NewClient(
		"localhost:50051",
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
	if err != nil {
		log.Fatalf("dial: %v", err)
	}
	defer conn.Close()

	client := pb.NewUserServiceClient(conn)

	// Unary call с таймаутом
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	resp, err := client.GetUser(ctx, &pb.GetUserRequest{Id: "42"})
	if err != nil {
		log.Fatalf("GetUser: %v", err)
	}
	log.Printf("user: %v", resp.User)

	// Server streaming call
	stream, err := client.ListUsers(ctx, &pb.ListUsersRequest{})
	if err != nil {
		log.Fatalf("ListUsers: %v", err)
	}
	for {
		user, err := stream.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatalf("stream.Recv: %v", err)
		}
		log.Printf("received: %s", user.Name)
	}
}

TLS-соединение

import "google.golang.org/grpc/credentials"

creds, err := credentials.NewClientTLSFromFile("ca.crt", "")
conn, err := grpc.NewClient("prod.example.com:443",
	grpc.WithTransportCredentials(creds),
)

Добавление метаданных (токен аутентификации)

import "google.golang.org/grpc/metadata"

ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+token)
resp, err := client.GetUser(ctx, req)

Подводные камни

  • grpc.NewClient() возвращает соединение немедленно, даже если сервер недоступен — первая реальная ошибка возникнет при RPC-вызове.
  • Не забывайте conn.Close() через defer; утечка соединений приводит к исчерпанию дескрипторов файлов.
  • Всегда устанавливайте таймаут через context.WithTimeout; без него клиент будет ждать бесконечно при зависшем сервере.
  • Для production необходимо credentials вместо insecure.NewCredentials(); insecure допустим только внутри доверенной сети.
  • По умолчанию gRPC-клиент выбирает один адрес из DNS; для балансировки нагрузки нужно явно установить grpc.WithDefaultServiceConfig с политикой round_robin.
  • Метаданные нужно добавлять к каждому вызову отдельно или через UnaryClientInterceptor — они не «прилипают» к conn.
  • При отмене контекста stream.Recv() вернёт ошибку с кодом codes.Canceled, а не io.EOF — обрабатывайте оба случая.

Common mistakes

  • Давать ответ про реализация gRPC-клиента на Go только на уровне определения, не показывая поведение в реальном приложении.
  • Игнорировать границы ответственности вокруг темы «реализация gRPC-клиента на Go»: кто отменяет работу, кто владеет ресурсом и где формируется ответ клиенту.
  • Не связывать реализация gRPC-клиента на Go с observability, тестированием или безопасностью, когда это влияет на продакшен-поведение.

What the interviewer is testing

  • Точно объясняет, что именно делает реализация gRPC-клиента на Go и где это используется в Go-коде.
  • Связывает реализация gRPC-клиента на Go с корректным lifecycle запроса, отменой, конкурентностью или конфигурацией сервера там, где это уместно.
  • Не изобретает API и опирается на реальные контракты официальной документации.

Sources

Related topics