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

Исходные условия: у нас было мобильное приложение, написанное на Objective-C (iOS) и Java (Android). Цель — переписать его на современный стек: Swift и Kotlin. Дополнительно нужно было сделать редизайн приложения и обновить бэкенд: поднять Java с 6 до 21. Приложение общалось с бэкендом по HTTP и использовало Protobuf для сериализации данных.

Что важно — никакой документации на существующее приложение не было. У нас была лишь тестовая сборка и сервер с логикой. Поэтому перед стартом разработки нужно было:

  1. Разобраться, как работает текущее приложение.

  2. Описать требования, чтобы разработчики могли писать код без кровавых слез.

  3. Настроить инфраструктуру для тестирования запросов.

Сериализация: JSON против Protobuf

Чтобы понять, почему Protobuf вызвал столько проблем, вспомним про сериализацию.

Сериализация — это упаковка данных в формат, который удобно хранить и передавать по сети. Помимо Protobuf, существуют и другие форматы сериализации данных — это, например, JSON и XML. Они очень популярны и широко применяются при разработке API. Главное их отличие от Protobuf состоит в том, что JSON и XML являются текстовыми форматами, а Protobuf — бинарным. Именно это различие в формате представления данных и определяет ключевые преимущества Protobuf по сравнению с текстовыми альтернативами.

Первое, на что стоит обратить внимание, — это размер данных. Так как Protobuf использует бинарное представление, он занимает существенно меньше памяти. Благодаря этому запросы и ответы передаются по сети быстрее. Но дело не только в размере: бинарная форма данных обеспечивает и скорость обработки.

Для примера возьмём JSON. Чтобы извлечь из него полезную нагрузку, парсеру приходится сначала пройтись по тексту, убрать все кавычки, скобки, преобразовать строки в числа или булевы значения, распарсить вложенные структуры. Только после этого данные становятся пригодны для работы внутри приложения. В случае с Protobuf этого дополнительного этапа нет: сериализованные бинарные данные уже содержат значения в том виде, в котором их легко преобразовать в объекты конкретного языка программирования.

Ещё одно важное преимущество Protobuf заключается в том, что он типизирован из коробки. JSON и XML сами по себе типизации не содержат. Чтобы добавить её, приходится использовать дополнительные надстройки — например, JSON Schema или XML Schema. В Protobuf необходимость в этом отпадает: вся типизация обеспечивается за счёт .proto-файлов.

кадр из фильма "Sex Education" режиссера Лори Нанн
кадр из фильма "Sex Education" режиссера Лори Нанн

Вот как выглядит такой .proto-файл. В нём можно описать конкретные объекты, которые будут передаваться по сети, указать их названия, задать типы полей и их опциональность. При необходимости можно добавить комментарии, которые станут частью документации. В .proto также поддерживаются вложенные объекты. Например, если у нас есть MessageUser, мы можем создать MessageUserProfile и добавить туда поле с типом User.

Обратите внимание ещё на один интересный момент. В .proto рядом с названиями полей всегда указываются цифры. Эти цифры используются при передаче данных по сети вместо текстовых названий полей. Такой подход дополнительно экономит место и позволяет передавать данные ещё компактнее. Позже мы увидим, как это работает на практике.

Главный минус: читаемость

Представим, что нам нужно отладить запрос: отправить его и посмотреть, какие именно данные пришли в ответе. Если это JSON, то всё очевидно: есть структура, есть поля с их названиями и значениями, и никакой дополнительной работы для понимания содержимого не требуется.

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

Именно в этом кроется главная сложность: там, где в JSON «читаемость из коробки», Protobuf без дополнительной расшифровки выглядит как «нечто непонятное». И именно этот недостаток в дальнейшем создал нам целый ряд проблем, с которыми пришлось разбираться уже в процессе работы над проектом. О решениях этих проблем я расскажу дальше.

Как мы разбирались с приложением без документации

Вернёмся к сути проекта. Основная задача заключалась в том, чтобы переписать мобильное приложение на новый стек технологий и поднять версию middleware. Но был важный нюанс: по старому приложению у нас не было никакой документации. Для начала нам нужно понять, как именно работает текущее приложение, чтобы зафиксировать документацию.

Зачем писать документацию для уже реализованного приложения? Во-первых, потому что переписывать код будут новые разработчики, и у них неизбежно появятся десятки вопросов. Вместо того чтобы тратить время на уточнения, они смогут найти хотя бы 70% ответов в требованиях — и это сильно ускорит процесс. Во-вторых, приложение изначально задумывалось как развивающийся продукт: со временем в него предполагалось добавлять новые функции и возможности. Чтобы такие доработки были системными и последовательными, требовалась чёткая база требований. В-третьих, после того как мы перепишем приложение и поднимем версию Java на middleware, нужно будет проверить, что всё работает так же, как раньше. А это значит, что потребуется инфраструктура для тестирования запросов. 

И прежде чем мы пойдем дальше, прежде чем мы обсудим все проблемы, с которыми мы столкнулись, все способы решения, которые мы рассмотрели, давайте попробуем прожить один день на проекте.

Один день на проекте

Типичный рабочий день. Ко мне подходит проектный менеджер Илья и говорит:
— Настя, на следующей неделе берём в разработку флоу авторизации и регистрации. Было бы здорово подготовить требования. Я соглашаюсь. Запускаю тестовую сборку, кликаю по кнопкам, изучаю экраны — поверхностное понимание есть. Но для описания требований этого недостаточно.

Большая часть логики лежит в интеграции мобильного приложения с backend: какие запросы отправляются, какие данные приходят и как они обрабатываются. Всё это тоже нужно отразить в требованиях.

Какие варианты у меня есть? Можно пойти к разработчикам и расспросить их. Но отнимать у них время не хочется. Можно самой полезть в код на Objective-C, но это непросто: разобраться можно, но процесс мучительный и медленный. Значит, остаётся третий вариант — сниффинг трафика.

Сниффинг трафика: первая попытка

Сниффинг позволяет увидеть, какие запросы и когда отправляются, какие данные передаются и какие приходят в ответе. Для этого мы использовали Proxyman (аналог — Charles).

Я подключила устройство, настроила сертификаты, запустила тестовую сборку — и действительно, запросы пошли. Казалось, половина работы сделана. Но когда я открыла содержимое запроса, то увидела не байты и не HEX, а «сырые» байты в escape-формате, так как проксимен не умеет автоматически десериализовать Protobuf без схемы.

Да, можно сопоставить цифры с номерами полей из .proto и вручную собрать структуру: цифра соответствует названию поля, через двоеточие идёт значение. Но вместе с этим в данных встречаются escape-последовательности, которые тоже нужно преобразовывать. Один запрос разобрать ещё можно, но их около 70. Это заняло бы огромное количество времени.

Промежуточный сервис: теоретическая идея

Теоретически проблему можно было решить с помощью отдельного сервиса. Он принимал бы JSON, преобразовывал его в Protobuf, отправлял на middleware, а ответ переводил обратно в JSON. Тогда всё стало бы читаемо.

Но такой сервис пришлось бы разрабатывать с нуля. Ради одной задачи — просмотра трафика. Мы отказались от этой идеи: слишком дорого и неоправданно.

Рабочее решение: .desc файлы

Решение оказалось проще. Мы взяли исходные .proto-файлы и прогнали их через утилиту protoc. Это компилятор, который умеет преобразовывать .proto в разные форматы: например, в Markdown или в .desc. Формат .desc — это те же .proto, но в бинарном виде. Он нам и понадобился. Почему? Потому что Proxyman не умеет работать напрямую с .proto, но поддерживает .desc.

После генерации мы загрузили .desc в Proxyman — и получили понятные ответы: видны поля, значения, структура. Всё читаемо. Проблема с анализом трафика была решена.

Документация API: чем заменить OpenAPI

Дальше встал вопрос: как документировать API.

Когда мы работаем в связке HTTP + JSON, источником правды является OpenAPI-файл. Он содержит все методы, описания запросов и ответов. Разработчики backend и frontend ориентируются именно на него. OpenAPI удобен ещё и тем, что из него можно генерировать код под разные языки.

Но в нашем случае был HTTP + Protobuf, и OpenAPI здесь не подходит: он поддерживает только JSON и YAML.

Что можно было сделать?

Идея 1: ссылки на GitLab

Можно в требованиях прикладывать ссылки на .proto в GitLab. Например, метод getUser использует UserRequest — вот ссылка. Но на практике UserRequest может содержать вложенные объекты (Address, Common и др.), которые лежат в других .proto файлах Чтобы собрать полную структуру, придётся лазить по разным файлам и вручную собирать конструктор. Это неудобно и медленно.

Решение: генерация Markdown

Мы снова воспользовались protoc, но преобразовали .proto не в .desc, а в Markdown.

Почему именно Markdown? Потому что мы писали требования в Confluence, а Confluence умеет импортировать .md.

Результат получился очень удобным: protoc автоматически создал таблички с полями, их типами и комментариями. Даже ссылки на вложенные объекты вставил сам. В Confluence это выглядело как полноценная спецификация API: можно кликнуть на объект и сразу перейти к его описанию.

кадр из фильма "Sex Education" режиссера Лори Нанн
кадр из фильма "Sex Education" режиссера Лори Нанн

Итоговый артефакт выглядел так: в требованиях к фиче указываем методы, их request и response со ссылками на страницы в Confluence, и примеры запросов/ответов (взятые из Proxyman). Этого оказалось достаточно и для разработки, и для тестирования.

Тестирование: как подружить Postman и Protobuf

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

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

Проблема №1: бинарные данные в запросах

Postman может принимать запросы в бинарном формате, но проблема в том, что мы не можем просто взять и описать запрос в этом самом бинарном формате. Для этого нам надо сначала описать JSON, а потом преобразовать его в Protobuf. 

С помощью protoc это можно сделать вручную: описать JSON-пейлоад, прогнать его через компилятор, получить бинар, вставить в тело запроса. Но дальше мы получаем ответ — и снова видим нечитаемую строку. Даже цифры, которые могли бы подсказать, к каким полям относятся значения, отсутствуют.

Проблема №2: расшифровка ответов

Формально можно было взять HEX-ответ (для этого нужно в Postman выбрать для Body тип отображения HEX), прогнать его через protoc и получить JSON. Но проделывать это для каждого запроса — значит заставить тестировщиков тратить на отладку больше времени, чем уходит на разработку.

Решение: pre-request и post-response в Postman

Чтобы не погрязнуть в ручной рутине, мы пришли к другому решению: использовать встроенные в Postman скрипты pre-request и post-response. Эти механизмы позволяют вставлять JavaScript прямо в запрос. Код в блоке pre-request выполняется до отправки запроса, а в post-response — сразу после получения ответа.

Подключение пакетов

Прежде чем переходить к самим скриптам, стоит сказать о ещё одной удобной возможности Postman — packages. Они позволяют вынести большой кусок JavaScript-кода в отдельный модуль и подключать его повторно в разных запросах.

Мы использовали библиотеку protobuf.js — полноценный инструмент для работы с Protobuf в JavaScript (около 9000 строк кода). Её мы добавили в отдельный пакет. Во второй пакет вынесли описание всех .proto-файлов, которые могут понадобиться при работе с запросами.

Если бы не было возможности подключать пакеты, нам пришлось бы вставлять эти тысячи строк кода в каждый запрос вручную. Коллекция превратилась бы в хаос: неудобно, нечитабельно, непрактично.

Логика pre-request 

// Подключаем packages

const protoSchema = pm.require('/proservice-proto');

const protobuf = pm.require('/protobufjs1');

// Получаем бинарный ответ от сервера в виде Buffer

const response = pm.response.stream;

// Ищем в .proto файле структуру, соответствующую нашему ответу

const root = protobuf.parse(protoSchema).root;

const MyMessage = root.lookupType('MergeProfileResponseProtobufDTO');

// Декодируем ответ и выводим в консоль Postman

console.info(MyMessage.decode(Buffer.from(response)))

Логика Post-response

// Подключаем packages

const protoSchema = pm.require('/proservice-proto');

const protobuf = pm.require('/protobufjs1');

// Получаем бинарный ответ от сервера в виде Buffer

const response = pm.response.stream;

// Ищем в .proto файле структуру, соответствующую нашему ответу

const root = protobuf.parse(protoSchema).root;

const MyMessage = root.lookupType('MergeProfileResponseProtobufDTO');

// Декодируем ответ и выводим в консоль Postman

console.info(MyMessage.decode(Buffer.from(response)))

В итоге тестировщик продолжает работать с привычным JSON, а вся магия преобразования происходит автоматически: Postman отправляет бинарник, а ответы расшифровываются в удобном виде через post-response.

Результаты

В итоге описанный подход нельзя назвать идеальным, но он оказался практичным и позволил ускорить работу аналитики и тестирования примерно в два-три раза. Почему это сработало? Мы сохранили привычные для себя инструменты, не стали переходить на новые технологии и тратить время на их изучение. Решение получилось простым, без зоопарка инструментов и лишней ручной работы.

Отдельный совет: если вы используете Protobuf, лучше делать это в связке с gRPC. Да, мы пошли по этому сценарию, потому что у нас не было выбора. 

Большинство проблем, о которых я рассказала, просто не возникли бы в такой конфигурации. Postman изначально умеет работать с Protobuf, когда он используется вместе с gRPC. Насколько мне известно, Proxyman тоже поддерживает этот сценарий. Так что универсальная рекомендация звучит так: если Protobuf — то сразу gRPC.

Чек-лист инструментов, которые реально помогли

  • Proxyman — сниффинг трафика и расшифровка запросов через .desc.

  • Postman — автоматизация через pre-request и post-response скрипты, работа с пакетами.

  • protobuf.js — библиотека для сериализации/десериализации внутри Postman.

  • protoc — генерация .desc для Proxyman и Markdown для документации.

Этого набора хватило, чтобы превратить «чёрный ящик» бинарного формата в понятный и управляемый процесс.

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

Комментарии (2)


  1. RodionGork
    22.08.2025 10:20

    я хочу поделиться опытом работы с бинарными форматами сериализации, а именно с Protobuf,

    спасибо конечно, но америку открыть вам кажется не удалось :) в наши времена проект без протобуфа найти сложнее чем с ним :)

    неплохо было бы не сравнением с JSON заниматься а рассмотреть альтернативные "бинарные" форматы. всё таки протобуф жирен (и не очень красив, не то что котики)


  1. ZetaTetra
    22.08.2025 10:20

    В XML без типизации - это как включить в сравнение TSV и CSV...

    А где SOAP? Где XML-RPC?