.proto - источник правды о сообщениях и методах. В отличие от JSON, на провод уходят номера полей, а не имена. Поэтому совместимость начинается с дисциплины контракта.

Базовая структура

syntax = "proto3";

package orders.v1;

message GetOrderRequest {
  string id = 1;
}

message Order {
  string id = 1;
  string status = 2;
  int64 amount_minor = 3;
  string currency = 4;
}

amount_minor хранит копейки или центы, а не дробную сумму. Номер после = нельзя переиспользовать: по нему клиент читает данные.

optional и присутствие

В proto3 обычное поле не отличает «не прислали» от значения по умолчанию. Для частичного обновления это важно:

message UpdateOrderRequest {
  string id = 1;
  optional string status = 2;
}

optional добавляет отслеживание присутствия. Без него сервер может принять пустой статус за настоящий.

repeated, map и oneof

repeated описывает список, map - пары ключ-значение. oneof означает одно значение из нескольких вариантов:

message Payment {
  oneof method {
    string card_token = 1;
    string bank_account = 2;
  }
}

oneof проверяет активный вариант, а не заполняет все поля сразу.

enum

Значение enum передаётся числом. Его нельзя переименовывать: новый клиент может присвоить другое смысловое значение старому числу. Добавлять новые значения безопаснее, чем менять старые.

Удалённые поля и номера помечают reserved, чтобы никто не занял их снова.

Как это выглядит в Go

Для optional генератор создаёт указатель: nil означает «поле не прислали», а указатель на пустую строку - «прислали пустое». Поэтому обновление начинается с проверки указателя, а не сравнения со значением по умолчанию.

oneof тоже имеет активный вариант: сначала определяют его через сгенерированный switch, затем читают только это поле. Нельзя бездумно заполнять все варианты сразу - контракт перестанет быть однозначным.

Неизвестное значение enum не обязано быть ошибкой десериализации. Если клиент и сервер разных версий, на стороне приложения нужен явный default: либо отклонить значение, либо обработать как неизвестное, но не присваивать ему смысл другого статуса.

Коротко

  • Номера полей неприкосновенны.
  • optional нужен там, где присутствие отличается от умолчания.
  • oneof выбирает один вариант, repeated и map описывают коллекции.
  • Enum не переименовывают, удалённые поля резервируют.