Ответ
Обратная и прямая совместимость в gRPC достигается за счет правил эволюции схемы в Protocol Buffers (Protobuf), который используется для сериализации данных.
Основные правила для неломающих изменений:
-
Не изменять и не переиспользовать теги существующих полей. Тег (числовой идентификатор) — это ключ к данным в бинарном формате. Его изменение сломает парсинг у старых клиентов/серверов.
-
Добавление новых полей. Новые поля можно безопасно добавлять. Старые клиенты просто проигнорируют их при десериализации. Новые клиенты смогут с ними работать.
-
Удаление полей. Поля нельзя просто удалять из
.protoфайла. Вместо этого их следует помечать какreserved. Это предотвращает случайное переиспользование тега в будущем, что могло бы привести к ошибкам.message MyMessage { reserved 2, 15, 9 to 11; reserved "old_field_name"; } -
Переименование полей. Переименование поля безопасно, так как 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, это как контракт между клиентом и сервером. И главный герой тут — не имя поля, а его числовой тег. Это как татуировка на всю жизнь, хуле. Запомнили номер — и всё, по нему и опознают.
Как не сломать всё к херам, когда меняешь схему:
-
Теги полей — священная корова. Тот номер, который вы однажды присвоили полю
user_id = 1, — это навсегда. Менять его — это всё равно что поменять замки в квартире, не сказав жильцам. Придут, ключ не подойдёт — пиздец. Не делайте так. -
Хотите новую фичу — добавьте новое поле. Это самое безопасное. Старый клиент получит ваше новое послание, увидит незнакомый тег, пожмёт плечами и проигнорирует. «О, какая-то хуйня с тегом 999, да похуй», — подумает он и продолжит работать. Новый клиент, зная про это поле, будет его использовать. Красота.
-
Решили, что поле устарело и его надо удалить? Нельзя просто взять и вычеркнуть его из
.protoфайла. А то через полгода какой-нибудь новый разработчик, не в теме, возьмёт и использует этот освободившийся тег для своей новой фичи. И поехало: старые клиенты, получая данные, будут думать, что это старое поле, и попытаются его распарсить... Ёперный театр, ошибки, падения. Поэтому поле нужно помечать какreserved. Это как поставить табличку «место занято, не хуй сюда лезть».message MyMessage { reserved 2, 15; // Эти номера тегов — табу reserved "old_field_name"; // И это имя тоже больше не использовать } -
Переименование — вообще не проблема. Потому что в бинарном потоке имена полей не летают. Летают только теги. Хоть назовите поле
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полями вproto2(вproto3их, к счастью, уже нет). Удалишьrequiredполе — старый клиент будет ждать его, не получит и обидится навсегда.
Вот и вся магия. Главное — не трогать то, что уже работает, а новые фичи прикручивать новыми полями. И будет вам счастье, а не волнение ебать.