Объявления расширений
Подробно описывает, что такое объявления расширений, зачем они нужны и как их использовать.
Введение
На этой странице подробно описано, что такое объявления расширений, зачем они нужны, и как их использовать.
{{% alert title="Примечание" color="note" %}} Proto3 не поддерживает расширения (за исключением объявления пользовательских опций). Расширения полностью поддерживаются в proto2 и редакциях хотя.{{% /alert %}}
Если вам нужно введение в расширения, прочитайте это руководство по расширениям
Мотивация
Объявления расширений направлены на нахождение золотой середины между обычными полями и расширениями. Как и расширения, они избегают создания зависимости от типа сообщения поля, что, следовательно, приводит к более простому графу сборки и меньшим бинарным файлам в средах, где неиспользуемые сообщения трудно или невозможно отсечь. Как и обычные поля, имя/номер поля появляются в содержащем сообщении, что упрощает избежание конфликтов и позволяет увидеть удобный список того, какие поля объявлены.
Перечисление занятых номеров расширений с помощью объявлений расширений упрощает пользователям выбор доступного номера расширения и избежание конфликтов.
Использование
Объявления расширений являются опцией диапазонов расширений. Как предварительные
объявления в C++, вы можете объявить тип поля, имя поля и мощность
(одиночное или повторяющееся) поля расширения без импорта файла .proto,
содержащего полное определение расширения:
edition = "2023";
message Foo {
extensions 4 to 1000 [
declaration = {
number: 4,
full_name: ".my.package.event_annotations",
type: ".logs.proto.ValidationAnnotations",
repeated: true },
declaration = {
number: 999,
full_name: ".foo.package.bar",
type: "int32"}];
}
Этот синтаксис имеет следующую семантику:
- Несколько
declarationс различными номерами расширений могут быть определены в одном диапазоне расширений, если размер диапазона позволяет. - Если есть какое-либо объявление для диапазона расширений, все расширения диапазона также должны быть объявлены. Это предотвращает добавление необъявленных расширений и обеспечивает использование объявлений для любого нового расширения в этом диапазоне.
- Данный тип сообщения (
.logs.proto.ValidationAnnotations) не нуждается в предварительном определении или импорте. Мы проверяем только, что это допустимое имя, которое потенциально может быть определено в другом файле.proto. - Когда этот или другой файл
.protoопределяет расширение этого сообщения (Foo) с этим именем или номером, мы обеспечиваем, чтобы номер, тип и полное имя расширения совпадали с тем, что предварительно объявлено здесь.
{{% alert title="Предупреждение" color="warning" %}}
Избегайте использования объявлений для групп диапазонов расширений, таких как extensions 4, 999.
Неясно, к какому диапазону расширений применяются объявления, и в настоящее время
это не поддерживается.{{% /alert %}}
Объявления расширений ожидают два поля расширения с разными пакетами:
package my.package;
extend Foo {
repeated logs.proto.ValidationAnnotations event_annotations = 4;
}
package foo.package;
extend Foo {
optional int32 bar = 999;
}
Зарезервированные объявления
Объявление расширения может быть помечено reserved: true, чтобы указать, что оно
больше не активно используется и определение расширения было удалено. Не
удаляйте объявление расширения и не редактируйте его значения type или full_name.
Этот тег reserved отделен от зарезервированного ключевого слова для обычных полей и
не требует разбиения диапазона расширений.
edition = "2023";
message Foo {
extensions 4 to 1000 [
declaration = {
number: 500,
full_name: ".my.package.event_annotations",
type: ".logs.proto.ValidationAnnotations",
reserved: true }];
}
Определение поля расширения, использующее номер, который reserved в
объявлении, не скомпилируется.
Представление в descriptor.proto
Объявление расширения представлено в descriptor.proto как поля в
proto2.ExtensionRangeOptions:
message ExtensionRangeOptions {
message Declaration {
optional int32 number = 1;
optional string full_name = 2;
optional string type = 3;
optional bool reserved = 5;
optional bool repeated = 6;
}
repeated Declaration declaration = 2;
}
Поиск полей через рефлексию
Объявления расширений не возвращаются из обычных функций поиска полей
таких как Descriptor::FindFieldByName() или Descriptor::FindFieldByNumber(). Как и
расширения, они обнаруживаются процедурами поиска расширений, такими как
DescriptorPool::FindExtensionByName(). Это явный выбор, который
отражает тот факт, что объявления не являются определениями и не имеют достаточной
информации для возврата полного FieldDescriptor.
Объявленные расширения все еще ведут себя как обычные расширения с точки зрения TextFormat и JSON. Это также означает, что миграция существующего поля на объявленное расширение потребует сначала миграции любого рефлексивного использования этого поля.
Используйте объявления расширений для выделения номеров
Расширения используют номера полей так же, как это делают обычные поля, поэтому важно чтобы каждому расширению был назначен номер, уникальный в пределах родительского сообщения. Мы рекомендуем использовать объявления расширений для объявления номера поля и типа для каждого расширения в родительском сообщении. Объявления расширений служат реестром всех расширений родительского сообщения, и protoc будет обеспечивать отсутствие конфликтов номеров полей. Когда вы добавляете новое расширение, выберите следующий доступный номер, обычно просто увеличив на единицу предыдущий добавленный номер расширения.
СОВЕТ: Существует особое руководство для
MessageSet, которое предоставляет скрипт для помощи в выборе
следующего доступного номера.
Всякий раз, когда вы удаляете расширение, убедитесь, что пометили номер поля как reserved,
чтобы устранить риск случайного повторного использования
его.
Эта конвенция является лишь рекомендацией - команда protobuf не имеет возможности или желания заставлять кого-либо придерживаться ее для каждого расширяемого сообщения. Если вы, как владелец расширяемого proto, не хотите координировать номера расширений через объявления расширений, вы можете выбрать координацию другими средствами. Однако будьте очень осторожны, потому что случайное повторное использование номера расширения может вызвать серьезные проблемы.
Один из способов обойти проблему - полностью избегать расширений и использовать
google.protobuf.Any
вместо этого. Это может быть хорошим выбором для API, которые работают с хранилищем, или для
сквозных систем, где клиент заботится о содержимом proto, но
система, получающая его, - нет.
Последствия повторного использования номера расширения
Расширение - это поле, определенное вне содержащего сообщения; обычно в отдельном файле .proto. Такое распределение определений делает легким для двух разработчиков случайно создать разные определения для одного и того же расширения номера поля.
Последствия изменения определения расширения одинаковы для расширений и стандартных полей. Повторное использование номера поля вводит неоднозначность в то, как proto должен декодироваться из формата провода. Формат провода protobuf является lean и не предоставляет хорошего способа обнаружения полей, закодированных с использованием одного определения и декодированных с использованием другого.
Эта неоднозначность может проявиться в короткие сроки, например, когда клиент использует одно определение расширения, а сервер использует другое при общении .
Эта неоднозначность также может проявиться в течение более длительного периода времени, например, при хранении данных закодированных с использованием одного определения расширения и последующем извлечении и декодировании с использованием второго определения расширения. Этот долгосрочный случай может быть трудным для диагностики, если первое определение расширения было удалено после того, как данные были закодированы и сохранены.
Результатом этого может быть:
- Ошибка разбора (лучший сценарий).
- Утечка PII / SPII – если PII или SPII записываются с использованием одного определения расширения и читаются с использованием другого определения расширения.
- Повреждение данных – если данные читаются с использованием «неправильного» определения, изменяются и перезаписываются.
Неоднозначность определения данных почти наверняка будет стоить кому-то времени на отладку как минимум. Это также может вызвать утечку или повреждение данных, на устранение которых уйдут месяцы.
Советы по использованию
Никогда не удаляйте объявление расширения
Удаление объявления расширения открывает дверь для случайного повторного использования в будущем. Если расширение больше не обрабатывается и определение удалено, объявление расширения может быть помечено как зарезервированное.
Никогда не используйте имя поля или номер из списка reserved для нового объявления расширения
Зарезервированные номера могли использоваться для полей или других расширений в прошлом.
Использование full_name зарезервированного поля
не рекомендуется
из-за
возможности неоднозначности при использовании textproto.
Никогда не меняйте тип существующего объявления расширения
Изменение типа поля расширения может привести к повреждению данных.
Если поле расширения имеет тип enum или message, и этот enum или message переименовывается, обновление имени объявления требуется и безопасно. Чтобы избежать поломок, обновление типа, определения поля расширения и объявления расширения должно произходить в одном коммите.
Будьте осторожны при переименовании поля расширения
Хотя переименование поля расширения допустимо для формата провода, это может сломать парсинг JSON и TextFormat.