Как работать с protobuf в Go

Аватар Типичный программист

Статья расскажет об использовании protobuf — механизме, придуманном Google для сериализации структур данных.

E-commerce давно перестали быть сайтами с картинками — сегодня это огромные онлайн-платформы с множеством высоконагруженных сервисов. В Ozon порядка 60% сервисов — от инфраструктурных проектов до пользовательских — написано на Go, в IT-лаборатории компании сейчас одна из самых больших golang-команд России. Об инструментах разработки и трендах в развитии языка рассказывает Владимир Сердюков, ведущий разработчик группы «Личный кабинет» Ozon.

Как-то ко мне пришел тестировщик и показал тестовый смартфон, на котором было запущено наше приложение — но вместо текста и элементов интерфейса на нем был белый экран. Мы начали разбираться.

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

Почему эту проблему стоит обсуждать?

Страница сайта и экран мобильного приложения состоят из множества виджетов. Каждый из них — это контракт, то есть договоренность, в каком виде он придет от бэкенда и как его показывать на клиенте. Если в контракте будет ошибка, это может нарушить работоспособность целого приложения (на самом деле уже нет, но раньше могло).

Подобные проблемы сложно локализовать, и они не заметны на этапе тестирования. Как же тогда их решить? Я бы рекомендовал следующее:

  • использовать один контракт для разных платформ (в одном виде для desktop, приложения и т.д.);
  • хранить контракты в одном репозитории;
  • генерировать код под разные платформы (не писать вручную, забыть про копипасту).

proto 2 vs proto 3

Для работы с контрактами мы в Ozon используем protobuf — механизм, придуманный Google для сериализации структур данных. Чтобы из proto-файлов сгенерировать код, мы используем proto 3 с набором плагинов, один из которых — gogoproto, призванный упростить этот процесс и частично забороть особенности proto v3.

Предыдущая версия протокола (proto 2) позволяла реализовывать обязательные поля при помощи тега optional и задавать стандартное значение. Однако это приводило к проблемам портирования возможности генерации кода на другие языки программирования.

Это стало причиной появления proto 3. В новой версии протокола поменялось отношение к обязательным полям: все поля стали необязательные, а значения по умолчанию просто не отправляются. Кроме того, были исправлены enum, улучшен декодинг в json и внесены другие мелкие изменения.

Как сделать поля обязательными в proto 3

Чтобы сделать поля обязательными, можно использовать Well-known типы данных. Это может быть строка, булево значение, число, timestamp и тому подобное.

Как работать с protobuf в Go 1

Если появляется такая строка, значит поле отправится вне зависимости от того, что вы в него записали. Однако сложные типы данных (proto-сообщение) все равно остаются необязательными. Тут на помощь приходит плагин gogoproto, где можно указать, что ваша структура не nullable.

Как работать с protobuf в Go 2

Кроме того, можно сделать свой собственный форк jsonpb. Это библиотека, которая proto-сообщения генерирует в json. В нее добавлены три вида тегов — написав их в любом месте файла, можно не переживать, что сообщение не будет отправлено.

Как работать с protobuf в Go 3

Как проверить, что новые изменения не ломают контракты?

Теперь понятно, как можно сделать поля обязательными, но что делать, если вдруг поле перестало быть обязательным или произошло что-то еще?

Для этого есть несколько инструментов для валидации proto-файлов, например buf.build и uber/prototool.

Оба инструмента работают по схожей схеме: чтобы запустить валидацию, создайте yaml-файл, в нем укажите конфигурацию, какие proto-файлы надо проверить, а какие нет. Также можно указать, какую ветку из репозитория брать. В обоих случаях работа идет только с git.

uber/protool пока не поддерживает apiv2, поэтому стоит приглядеться внимательнее к buf.build.

Что такое apiv2?

Поддержка работы с protobuf в Go впервые была анонсирована в 2010 году, первая версия Go вышла только спустя два года. С тех пор прошло много времени, изменились требования.

В итоге, несколько месяцев назад, 2 марта 2020 года, была анонсирована apiv2. Вот что изменилось по сравнению с прошлой версией:

  • появилась рефлексия для proto.Message;
  • канонический маппинг для json-полей;
  • улучшение производительности;
  • не получится использовать старые плагины и инструменты.

Давайте рассмотрим пример того, что теперь можно сделать из коробки. Есть api, возвращающее данные, которые мы не хотим показывать в логах (например, пароли пользователей, номера банковских карт и т.п.).

Как работать с protobuf в Go 4

Для этого подключаем специальный пакет google.protobuf.FieldOptions и сразу добавляем флаг non_sensitive. Он будет отвечать за то, нужно ли маскировать данные внутри ответа. Выставим по умолчанию значение false (привет, proto2).

Дальше в функции-обработчике где-нибудь в середине формирования сообщения делаем следующее:

Как работать с protobuf в Go 5

Подключаем новый интерфейс — ProtoReflect() и используем функцию Range().

Таким образом проходимся по все кастомным полям и проверяем значение non_sensitive поля (конечно же, используя рефлексию). Как только находим поле, в котором нужно убрать данные, вызываем Clear().

Таким нехитрым способом можно реализовать разные бизнес-кейсы.

Что в итоге?

  • В новых проектах используйте proto3.
  • apiv2 для Go не просто можно, но и нужно использовать.
  • Не забывайте валидировать свои proto-файлы.
  • Buf.build выглядит перспективно.
  • Плагины для генерации go-кода не актуальны, но можно увидеть много проектов, где они еще используются.
API
Веб-разработка
Golang
Для продвинутых
7867