← Назад к вопросам

Писали ли свои протофайлы для gRPC

2.3 Middle🔥 231 комментариев
#Сетевые протоколы и API

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Да, конечно! Как Go разработчик с опытом создания микросервисных архитектур, я регулярно писал свои .proto файлы для gRPC. Это стандартная и необходимая практика при разработке систем на основе gRPC, поскольку именно proto файлы определяют контракт между сервисами.

Что такое Protocol Buffers и .proto файлы?

Protocol Buffers (protobuf) — это язык описания интерфейсов (IDL) и механизм сериализации данных, разработанный Google. .proto файлы — это исходные файлы, в которых вы определяете структуры данных (сообщения) и методы (сервисы). Из этих файлов компилятор protoc генерирует код для клиента и сервера.

Пример простого .proto файла для микросервиса "Управление пользователями"

// user_service.proto
syntax = "proto3";

package user.v1;

import "google/protobuf/timestamp.proto";

// Описание сервиса с методами
service UserService {
  rpc CreateUser(CreateUserRequest) returns (UserResponse);
  rpc GetUser(GetUserRequest) returns (UserResponse);
  rpc UpdateUser(UpdateUserRequest) returns (UserResponse);
  rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
}

// Определение структуры сообщения "Запрос на создание пользователя"
message CreateUserRequest {
  string email = 1;
  string name = 2;
  Role role = 3;
}

// Определение структуры сообщения "Ответ с данными пользователя"
message UserResponse {
  string id = 1;
  string email = 2;
  string name = 3;
  Role role = 4;
  google.protobuf.Timestamp created_at = 5;
}

// Определение структуры для запроса получения пользователя по ID
message GetUserRequest {
  string id = 1;
}

// Определение структуры для запроса обновления пользователя
message UpdateUserRequest {
  string id = 1;
  optional string email = 2; // Поле стало optional в proto3 с использованием ключевого слова
  optional string name = 3;
  optional Role role = 4;
}

// Определение структуры для запроса списка пользователей
message ListUsersRequest {
  int32 page = 1;
  int32 limit = 2;
  optional Role filter_role = 3;
}

// Определение структуры ответа со списком пользователей
message ListUsersResponse {
  repeated UserResponse users = 1;
  int32 total_count = 2;
  int32 page = 3;
}

// Определение enum для ролей пользователей
enum Role {
  ROLE_UNSPECIFIED = 0;
  ROLE_USER = 1;
  ROLE_ADMIN = 2;
  ROLE_MODERATOR = 3;
}

Практические шаги и соображения при написании .proto файлов в Go проекте

  1. Структура проекта и версионирование: Я всегда организовывал proto файлы в отдельной директории (например, /proto) и использовал версионирование в названии пакета (package user.v1), чтобы обеспечить совместимость при будущих изменениях API.
  2. Планирование сообщений: Важно заранее продумать все сообщения для запросов и ответов. Я старался разделять сообщения для разных методов, даже если они похожи, чтобы обеспечить гибкость и независимое изменение API каждого метода в будущем.
  3. Использование типов данных: proto3 поддерживает различные типы: string, int32, int64, bool, bytes, а также другие сообщения и enum. Для полей даты и времени удобно использовать импортированный тип google.protobuf.Timestamp.
  4. Семантика номеров полей: Номера полей (например, string email = 1;) — это не значения, а уникальные идентификаторы для сериализации. Их нельзя менять после начала использования API, так как это нарушит совместимость.
  5. Optional поля в proto3: В proto3 все поля по умолчанию "optional" с точки зрения наличия значения, но для явного указания "nullable" поведения (поле может быть не задано) используется ключевое слово optional, как показано в примере выше.

Генерация Go кода из .proto файлов

После создания .proto файла необходимо использовать компилятор protoc вместе с Go плагинами (protoc-gen-go и protoc-gen-go-grpc) для генерации Go кода.

# Пример команды генерации для Go
protoc \
  --proto_path=./proto \
  --go_out=./internal/gen \
  --go-grpc_out=./internal/gen \
  ./proto/user_service.proto

Эта команда создает в директории ./internal/gen два файла:

  • user_service.pb.go — содержит код для сериализации/десериализации всех сообщений (структуры Go, соответствующие CreateUserRequest, UserResponse и т.д.).
  • user_service_grpc.pb.go — содержит интерфейсы и код для сервиса (UserServiceClient для клиента и UserServiceServer для сервера).

Интеграция с кодом Go сервиса

На стороне сервера нужно реализовать интерфейс, сгенерированный в user_service_grpc.pb.go.

// internal/server/user_server.go
package server

import (
    "context"
    pb "yourproject/internal/gen/user/v1" // импорт сгенерированного пакета
)

type userServer struct {
    pb.UnimplementedUserServiceServer // важно встраивать этот тип для совместимости с будущими расширениями
    // ... поля вашего сервиса (репозиторий, логгер и т.д.)
}

// Реализация метода CreateUser
func (s *userServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.UserResponse, error) {
    // 1. Валидация данных из req (email, name, role)
    // 2. Преобразование protobuf сообщения в вашу внутреннюю модель (например, UserEntity)
    // 3. Вызов бизнес-логики или репозитория для сохранения пользователя
    // 4. Преобразование результата (UserEntity) в protobuf ответ (*pb.UserResponse)
    // 5. Возврат ответа и ошибки (nil или gRPC статус)
    userEntity := &UserEntity{
        Email: req.Email,
        Name:  req.Name,
        Role:  Role(req.Role), // преобразование enum
    }

    savedUser, err := s.repository.Save(ctx, userEntity)
    if err != nil {
        return nil, status.Errorf(codes.Internal, "failed to save user: %v", err)
    }

    response := &pb.UserResponse{
        Id:        savedUser.ID,
        Email:     savedUser.Email,
        Name:      savedUser.Name,
        Role:      pb.Role(savedUser.Role),
        CreatedAt: timestamppb.New(savedUser.CreatedAt),
    }
    return response, nil
}

Ключевые уроки и лучшие практики

  • Контракт прежде всего: .proto файлы — это публичный контракт API. Их изменение (особенно удаление полей или изменение их номеров) требует внимательности и может нарушить работу существующих клиентов.
  • Версионирование: Использование версий в пакетах (v1, v2) позволяет постепенно внедрять новые API, сохраняя старые для обратной совместимости.
  • Использование oneof: Для сценариев, где запрос или ответ могут иметь разные варианты структуры, очень полезен тип oneof. Например, message UpdateRequest { oneof update { EmailUpdate email; NameUpdate name; } }.
  • Грамотная обработка ошибок: gRPC имеет свою систему статусов (codes.Internal, codes.NotFound и т.д.). Ошибки бизнес-логики нужно преобразовывать в соответствующие gRPC статусы с помощью status.Errorf.
  • Документация в .proto файлах: Добавление комментариев в .proto файлы (// Комментарий) очень полезно, так как они могут быть автоматически включены в документацию API.

Таким образом, писать свои .proto файлы для gRPC — это не только стандартная практика, но и важнейший этап проектирования коммуникации между микросервисами. Это обеспечивает четкое определение API, автоматическую генерацию клиентского и серверного кода, а также эффективную сериализацию данных благодаря компактному бинарному формату protobuf. В Go экосистеме инструменты для работы с gRPC и protobuf отлично развиты, что делает этот процесс эффективным и продуктивным.

Писали ли свои протофайлы для gRPC | PrepBro