Обновить
Комментарии 30
Понимаю что это перевод, но всё же, зачем выступать против? Выступайте за… что нибудь))) Конечно ProtoBuf развивался стихийно, этим объясняется несовершенство его системы типов.
Вопрос, а какие есть альтернативы, которые обеспечат такую же статическую типизацию (на основе кодогенерации) и производительность?
У него все еще хуже и скорость совсем не та, ну по крайней мере было 3 года назад когда мне пришлось очень близко с ним познакомиться.
И да он больше про RPC чем про эффективную сериализацию.

Смотря для каких целей. И если есть поддержка языков.
Гонять данные в/из браузера — отлично.
Между системами — есть поддержка множества типов, можно добавить свои,
передал дату-время — получил дату-время.


Вы серьезно?))

А в чем вопрос?

Производительность и типизация — это основная функциональность подобных систем. В Protobuf проделана огромная работа, чтобы выжать производительность до максимума. А вы показываете проектик с 87 коммитами заброшенный больше года назад.

А тем кому надо передать дату-время туда-сюда protobuf не нужен.
Почему в тэгах XML, если в тексте ни разу не упоминается? Мы должны догадаться, что вместо protobuf автор предлагает использовать XML?
Мы вообще живем в мире прототипов которые волевым усилием отправили в продакшен.
Сериализация в общем и протобуф в частности могут использоваться в разных ситуациях. В некоторых из них важна прямая и обратная совместимость, в других только обратная, где-то совместимость между версиями вообще не нужна.

Это же относится к следующему пассажу.
Вариант 1 — однозначно «правильное» решение, но оно непригодно для Protobuffers. Язык недостаточно мощный для кодирования типов, которые могут выполнять двойную работу в двух форматах. Это означает, что вам придётся написать совершенно отдельный тип данных, развивать его синхронно с Protobuffers и специально писать код сериализации для них. Но поскольку большинство людей, кажется, используют Protobuffers, чтобы не писать код сериализации, такой вариант, очевидно, никогда не реализуется.


В зависимости от приложения и ситуации вариант №1 может оказаться единственно возможным решением, а где-то применим вариант №2 или №3.

Теперь касательно «большинство людей, кажется, используют Protobuffers, чтобы не писать код сериализации».

Проблема не в том чтобы написать код сериализации. Написать код сериализации сама по себе простая задача, тем более если весь код заключается в копировании данных из «обыкновенного» класса в сгенерированный протобуфом. Основная проблема сериализации заключается как раз в поддержании совместимости между старыми и новыми версиями классов и данных. И здесь протобуф справляется очень хорошо, пусть и путём ввода некоторых ограничений и фишек вроде required и optional.

Например, boost::serialization справляется с задачей обратной совместимости, но поддержки прямой совместимости нет, хотя соответствующие багрепорты открыты давно. Мне пришлось написать специальную библиотеку для того чтобы реализовать прямую совместимость хотя бы отчасти.
пусть и путём ввода некоторых ограничений и фишек вроде required и optional.
Ващет выпилили. Теперь все филды всегда optional.

Required мешает выкидывать поля в новой версии, то есть мешает прежде всего прямой(forward) совместимости. Для обратной совместимости это небольшая проблема. Конечно, для приложений, где не требуется прямая совместимость required был бы полезен.

Насчёт значений по умолчанию для скаляров это грамотное решение. Опциональность приехала из языков с динамической типизацией, где есть null (да и то не для всех это применимо, явный nil например не может быть значением в Lua таблице). И кроме json я что то не припомню форматов кодирования с explicit null значениями. За опциональность надо платить и пусть это будет выражено в явном виде. Мне кажется со стороны Google это был реверанс в сторону C/C++.
Критикуешь — предлагай. Какие ещё есть сериализаторы, имеющие биндинги под С, позволяющие гонять данные между ARM32 и 64битной виндозиной, и умеющие не кодировать в пакет поля класса со значением по-умолчанию. Когда выбирал в проект, на последнем пункте срезалось всё, что предлагали коллеги. Но дело было лет 5 тому назад.
ASN.1 BER/DER. BSON. Avro. Thrift. Bycycle в конце концов.
Капитан прото возможно тоже будет неплохим выбором.

Cap n proto еще в сто раз хуже, там все делается через указатели и можно создавать циклы, с которыми предлагают бороться ограничивая глубину рекурсии.

Пользую protobuf 2 и 3, как и позднее grpc, много лет в разных проектах на С++, меньше на python. Проблемы автора, признаться, не очень понятны.
Поле не может быть repeated? Оберни его в message, и оно сможет, оверхед в С++ будет примерно нулевой.
Значения по умолчанию? 0, и ничего другого, RTFM.


Не идеально, конечно же, в том же С++ коде профайлер показывает огромное, по сравнению на пример с flatbuffers, количество выделений памяти. Но покажите мне что-нибудь получше, чтоб из коробки понимало хотя бы С++, java, python? Go, С# и Rust желательны, но сейчас не обязательны :)

Смешаны в кучу претензии к спецификации схемы и к reference implementation кодогенератора. Никто не заставляет использовать protoc от Google.


Лично моё мнение:


  • Заточенность системы типов на типичные случаи использования — это не так уж и плохо. Почти все описанные проблемы решаются обёртыванием в отдельный тип.
  • Работа с опциональными типами в сгенерированном гугловым компилятором коде действительно ужасна: эти hasFoo() и getFoo() с дефолтными значениями — прямой путь к неожиданному поведению кода вместо вылета NullPointerException. Значение по умолчанию практически никогда не имеет смысла — какие полезные операции можно сделать с объектом, у которого во всех полях нули, пустые строки и вложенные такие же пустые объекты? Это выглядит дико даже в Java, не говоря о языках со встроенными средствами работы с опциональными значениями.
  • Proto3 пошёл ещё дальше и теперь такая же ситуация в спецификации, так как required полей больше нет. А уж что там происходит с enum — это вообще нонсенс. Не указал значение — получаешь первое объявленное. И нет способа узнать, было ли оно установлено или это "дефолтное". В результате десериализации можно получить всё что угодно — любое поле могло быть не задано и код будет по-тихому работать не так, как задумано.
    По мне так такая "схема данных" — это просто мусор.

То, что из-под крыла Google выходит нечто сомнительного качества, я вижу не в первый раз. Впрочем, что ещё можно ожидать от большой корпорации, в которой разными продуктами занимаются совершенно разные люди с разными целями, умениями и бюджетами.

Конкретно по enum решение простое.
Объявить первым undefined и появится возможность определить было оно задано или нет

Да, так и делаем. Но это же ужасно, вам не кажется?

Сложный вопрос.


Как лучше?
Один метод save() или два add() & update()?

Конечно, реальная логика сериализации позволяет делать что-то умнее, чем пушить связанные списки по сети — в конце концов, реализация и семантика не обязательно должны соответствовать друг другу.

Вот только в protobuf-описании задается именно реализация, а не семантика! Одно из требований к библиотеке для сериализации — это бинарная совместимость с другими библиотеками сериализации работающими с тем же самым форматом.


А значит, если определить список через сопродукт — то он будет передан по сети именно как сопродукт. То есть без указания количества элементов и с обязательным разделителем между элементами. И никакой оптимизации тут добавить нельзя, потому что формат сообщений не должен зависеть от настроек оптимизации.


Невозможно отличить поле, которое отсутствовало в протобуфере, от поля, которому присвоено значение по умолчанию.

Так ведь так и задумано же. И это сделано не для хитрых оптимизаций, а ради возможности обновлять протокол оставаясь совместимым со старой версией.


Protobuffers в духе Java различает скалярные типы и типы сообщений. Конечно же, в двух разновидностях типов совершенно разная семантика.

Вот как автор это углядел? Я, напротив, вижу что приведенный псевдокод написан как раз из желания придать сообщениям скалярную семантику. Именно потому null "втихую" и заменяется на пустое сообщение.


Но если вы измените foo, он также изменит своего родителя!

Обычное поведение ссылочных типов данных в императивных языках. При чем тут вообще protobuf?


Мы ожидаем, что задание msg.foo = msg.foo; не будет работать.

Так оно и не работает...


Обратите внимание, что, по крайней мере, в языках со статической типизацией, этот шаблон нельзя абстрагировать из-за номинальной связи между методами foo(), set_foo() и has_foo()

И чем же Reflection и FieldDescriptor — не абстракция? А ведь можно еще и свои кодогенераторы подключать...


Чтобы сменить тему, поговорим о другом сомнительном решении. Хотя вы можете в протобуферах определить поля oneof, их семантика не соответствует типу сопродукта! Ошибка новичка, парни! Вместо этого вы получаете опциональное поле для каждого случая oneof и магический код в сеттерах, который просто отменит любое другое поле, если это установлено.

Интересно, а какая еще возможна нормальная реализация сопродукта на C++? std::variant, к примеру, при некорректном обращении кидает исключение — то-то радости будет программисту, который не может уследить за тем, какие свойства он читает...

Нормальная реализация сопродукта делается через деструктурирование и pattern matching, а не через тыкания в стиле dynamic_cast.

Вот только последняя версия вышла за два месяца до C++17… Кроме того, старый интерфейс убирать уже нельзя — на него ведь существующий код завязан.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.