У Protocol Buffers у повідомленні передаються не назви полів, а їхні номери. Звідси головні правила еволюції схеми: важливо не те, як поле називається, а який у нього номер і тип.
Безпечні зміни:
- додати нове поле з новим номером. Старі клієнти його проігнорують, нові - отримають значення за замовчуванням, якщо старий сервер його не надіслав;
- перейменувати поле - номер той самий, на «дроті» нічого не змінилося (але згенерований код зміниться - це зміна API для коду, не для формату);
- видалити поле, якщо його номер і назву зарезервувати.
Небезпечні зміни:
- змінити номер поля - для формату це видалення старого поля і поява нового;
- повторно використати номер видаленого поля з іншим змістом - старі клієнти розберуть нові дані як старе поле, з тихим пошкодженням даних;
- змінити тип на несумісний (
string→int64). Деякі пари сумісні на рівні формату (int32/int64/bool), але з можливим обрізанням значень - покладатися на це не варто; - перетворити одиничне поле на
repeatedчи навпаки - з неочевидними наслідками для різних мов.
Резервування видалених полів:
message Invoice {
reserved 3, 7;
reserved "discount", "legacy_status";
int64 id = 1;
string number = 2;
int64 total_cents = 4;
}
Компілятор не дасть використати зарезервовані номери й назви повторно.
Інші практики:
- значення за замовчуванням у proto3 -
0,"",falseнеможливо відрізнити від «не задано». Якщо різниця важлива -optional(явна присутність) чи обгорткові типи; - перелічення: перше значення має бути
0і означати «невідомо» (STATUS_UNSPECIFIED = 0), бо саме його отримає старий клієнт для нових значень; - версія в назві пакета (
billing.v1,billing.v2) - для справді несумісних змін створюється новий пакет і обидва обслуговуються паралельно; - автоматична перевірка в CI: інструмент
buf breakingпорівнює схему з попередньою версією й знаходить ламаючі зміни до злиття.
Порядок розгортання: спершу оновлюються ті, хто читає нове поле (сервери, що його приймають), потім ті, хто починає його надсилати. Видалення - у зворотному порядку: спершу всі припиняють використовувати поле, потім воно резервується.
Докладніше в документації: Protocol Buffers: оновлення типу повідомлення