Каковы принципы обеспечения обратной и прямой совместимости в gRPC?

Ответ

Обратная и прямая совместимость в gRPC достигается за счет правил эволюции схемы в Protocol Buffers (Protobuf), который используется для сериализации данных.

Основные правила для неломающих изменений:

  1. Не изменять и не переиспользовать теги существующих полей. Тег (числовой идентификатор) — это ключ к данным в бинарном формате. Его изменение сломает парсинг у старых клиентов/серверов.

  2. Добавление новых полей. Новые поля можно безопасно добавлять. Старые клиенты просто проигнорируют их при десериализации. Новые клиенты смогут с ними работать.

  3. Удаление полей. Поля нельзя просто удалять из .proto файла. Вместо этого их следует помечать как reserved. Это предотвращает случайное переиспользование тега в будущем, что могло бы привести к ошибкам.

    message MyMessage {
      reserved 2, 15, 9 to 11;
      reserved "old_field_name";
    }
  4. Переименование полей. Переименование поля безопасно, так как Protobuf идентифицирует поля по их числовому тегу, а не по имени.

Пример совместимого изменения (добавление поля):

Версия 1:

message User {
  string id = 1;
  string name = 2;
}

Версия 2:

message User {
  string id = 1;
  string name = 2;
  // Новое опциональное поле
  optional string email = 3; 
}

Сервер, использующий Версию 2, может отправить User с полем email. Клиент с Версией 1 получит это сообщение, успешно распарсит поля id и name, а поле email просто проигнорирует.

Ломающие изменения (требуют новой версии API):

  • Изменение типа данных существующего поля.
  • Изменение номера тега существующего поля.
  • Удаление обязательного (required) поля (в proto2).

Ответ 18+ 🔞

Давайте разберем эту вашу совместимость в gRPC, а то я вижу, у некоторых уже глаза на лоб лезут от слова «эволюция схемы». Суть-то проще, чем кажется, если не выёбываться.

Вот смотрите. Всё держится на Protocol Buffers, это как контракт между клиентом и сервером. И главный герой тут — не имя поля, а его числовой тег. Это как татуировка на всю жизнь, хуле. Запомнили номер — и всё, по нему и опознают.

Как не сломать всё к херам, когда меняешь схему:

  1. Теги полей — священная корова. Тот номер, который вы однажды присвоили полю user_id = 1, — это навсегда. Менять его — это всё равно что поменять замки в квартире, не сказав жильцам. Придут, ключ не подойдёт — пиздец. Не делайте так.

  2. Хотите новую фичу — добавьте новое поле. Это самое безопасное. Старый клиент получит ваше новое послание, увидит незнакомый тег, пожмёт плечами и проигнорирует. «О, какая-то хуйня с тегом 999, да похуй», — подумает он и продолжит работать. Новый клиент, зная про это поле, будет его использовать. Красота.

  3. Решили, что поле устарело и его надо удалить? Нельзя просто взять и вычеркнуть его из .proto файла. А то через полгода какой-нибудь новый разработчик, не в теме, возьмёт и использует этот освободившийся тег для своей новой фичи. И поехало: старые клиенты, получая данные, будут думать, что это старое поле, и попытаются его распарсить... Ёперный театр, ошибки, падения. Поэтому поле нужно помечать как reserved. Это как поставить табличку «место занято, не хуй сюда лезть».

    message MyMessage {
      reserved 2, 15; // Эти номера тегов — табу
      reserved "old_field_name"; // И это имя тоже больше не использовать
    }
  4. Переименование — вообще не проблема. Потому что в бинарном потоке имена полей не летают. Летают только теги. Хоть назовите поле super_puper_field, а в коде оно было old_shitty_name — если тег один и тот же, всё будет работать. Переименовали для красоты? Молодцы.

Пример, как всё должно быть:

Было:

message User {
  string id = 1;
  string name = 2;
}

Стало (захотелось email):

message User {
  string id = 1;
  string name = 2;
  // Новое поле, все довольны
  optional string email = 3;
}

Сервер новой версии шлёт клиенту старой версии объект с email. Клиент старой версии видит тег 3, не знает, что это, пожимает плечами («да похуй») и читает только id и name. Ничего не сломалось. Обратная совместимость на лицо.

А вот что точно всё разъебёт (и потребует новой версии API, ссор и выяснения отношений):

  • Взять и поменять тип у существующего поля. Было int32, стало string — всё, пизда.
  • Переназначить тег, как я уже говорил. Это самоубийство.
  • Ковыряться с required полями в proto2proto3 их, к счастью, уже нет). Удалишь required поле — старый клиент будет ждать его, не получит и обидится навсегда.

Вот и вся магия. Главное — не трогать то, что уже работает, а новые фичи прикручивать новыми полями. И будет вам счастье, а не волнение ебать.