Расскажите о своем опыте работы с gRPC и Protocol Buffers. Как вы проектировали .proto файлы?

Ответ

Да, я активно использовал gRPC для построения взаимодействия между микросервисами. Основа gRPC — это описание контрактов с помощью Protocol Buffers (.proto файлов).

Мой опыт в проектировании .proto файлов включает:

  1. Определение сервисов и методов: Четко определяю сервисы (например, UserService, OrderService) и их RPC-методы (CreateUser, GetUser).

  2. Проектирование сообщений: Создаю сообщения (message) для запросов и ответов. Стараюсь делать их переиспользуемыми и логически сгруппированными.

  3. Версионирование API: Для обратной совместимости и плавного обновления сервисов я использую версионирование на уровне пакетов. Например, package myapi.v1;.

  4. Соблюдение обратной совместимости: При изменении .proto файлов я строго следую правилам:

    • Не изменять и не переиспользовать номера тегов существующих полей.
    • Добавлять новые поля только с новыми уникальными тегами.
    • Не удалять поля. Вместо этого можно использовать опцию deprecated=true.

Пример простого proto-файла:

syntax = "proto3";

// Версионирование через пакет
package user.v1;

// Опция для указания Go-пакета при генерации
option go_package = "github.com/my-org/my-project/gen/go/user/v1;userv1";

// Сервис для управления пользователями
service UserService {
  // Метод для создания пользователя
  rpc CreateUser (CreateUserRequest) returns (CreateUserResponse);
}

message User {
  string id = 1;
  string name = 2;
  int32 age = 3;
}

message CreateUserRequest {
  string name = 1;
  int32 age = 2;
}

message CreateUserResponse {
  User user = 1;
}

Процесс работы:

  1. После написания .proto файла я генерирую Go-код (серверные заглушки и клиентские стабы) с помощью protoc:

    protoc --proto_path=proto 
           --go_out=gen/go --go_opt=paths=source_relative 
           --go-grpc_out=gen/go --go-grpc_opt=paths=source_relative 
           proto/user/v1/user.proto
  2. Затем реализую сгенерированный интерфейс на стороне сервера и использую сгенерированный клиент для вызовов с других сервисов.

Я также знаком с более сложными возможностями Protobuf, такими как oneof для полей, которые могут принимать одно из нескольких значений, и map для словарей.

Ответ 18+ 🔞

Да ты посмотри, какой у нас тут эксперт по gRPC подъехал! Ну что ж, давай разберём твой опыт, а то я тут сижу, блядь, как Герасим немой, только "Му-му" могу сказать на эту тему.

Так, слушай сюда, протоколы эти твои. Я тоже, сука, вляпывался в эту историю с .proto файлами. Основа-то, конечно, гениальная — описал контракт, сгенерировал код, и поехали сервисы друг друга ебашить запросами. Красота!

Ты правильно говоришь про сервисы и методы. Это ж как в хорошем ресторане: есть меню — UserService, а в нём блюда — CreateUser, GetUser. Главное, блядь, не называть метод UserService.KickUserInTheAss, а то потом непонятки будут. Хотя, в зависимости от проекта...

А вот проектирование сообщений — это отдельный вид искусства, ёпта. Тут можно так навертеть, что потом сам от себя охуеешь. Создаёшь message, думаешь: "А добавлю-ка я поле optional string middle_name на всякий случай". А через полгода выясняется, что у половины пользователей отчества нет, а у другой половины их по три штуки! Пиздец, Карл! Надо было сразу repeated string patronymics делать. Опыт, блядь, больной такой.

Версионирование через пакет — это, конечно, святое. package myapi.v1; — и все сразу понимают, что если ты обновился до v2, то, скорее всего, тебе придётся переписать половину клиентов, потому что кто-то там required поле на optional поменял. Веселуха!

И да, обратная совместимость — это не просто слова, это религия. Твои правила — это библия:

  • Не изменять теги — это как не перепутать, где у человека рот, а где жопа. Поставил полю тег 1 — и ходи с ним до скончания веков.
  • Новые поля — новые теги — логично. Не лезь в чужой огород, сука.
  • Не удалять, а помечать как deprecated — вот это по-нашему, по-русски! Не можем убить — так хотя бы посмеёмся над старым полем, пусть все знают, что оно ни хуя не нужно.

Твой пример кода — ну классика жанра, чистая. Прям учебник. Я бы только, на твоём месте, int32 age на google.protobuf.Timestamp date_of_birth поменял. Потому что возраст, блядь, величина изменчивая, а дата рождения — константа. Но это так, придираюсь.

syntax = "proto3";
package user.v1;
option go_package = "github.com/my-org/my-project/gen/go/user/v1;userv1";

service UserService {
  rpc CreateUser (CreateUserRequest) returns (CreateUserResponse);
}

message User {
  string id = 1;
  string name = 2;
  // int32 age = 3; // Устарело, ёпта! Пользователь мог иметь день рождения!
  google.protobuf.Timestamp date_of_birth = 4;
}

message CreateUserRequest {
  string name = 1;
  google.protobuf.Timestamp date_of_birth = 2; // Вот так-то лучше!
}

message CreateUserResponse {
  User user = 1;
}

Ну и команда генерации... О, это отдельная песня! Запустил protoc с кучей флагов, ждёшь, а он тебе: "не найден плагин go-grpc". И сидишь, блядь, полдня гуглишь, как эту хуйню поставить. А когда всё сгенерируется — чувствуешь себя богом, который только что создал новый мир из протобуфера.

oneof и map — это уже для гурманов, да. oneof — когда поле может быть или email, или phone_number, но не оба сразу. А то вдруг пользователь — мартышлюшка какая-нибудь, у которой и того, и другого нет. А map — это когда тебе нужно срочно запихнуть хуй в пальто, то есть ассоциативный массив каких-нибудь метаданных.

Короче, опыт у тебя, я смотрю, обширный. Главное — не забывай, что за всеми этими тегами, пакетами и сервисами стоят живые люди, которые будут это всё поддерживать. А им, блядь, не всегда просто. Так что делай контракты понятными, как день, и стабильными, как швейцарские часы. И тогда всё будет пиздато.