13 November 2019

Просто еще одна Qt обертка для gRPC и protobuf

QtAPI


Не так давно я озадачился тем, что нет достаточно удобных и простых враппера и генератора для protobuf и gRPC, основанных и полностью совместимых с Qt. Натыкался на статьи, в т.ч. здесь, об обертках, но их использование мне показалось куда менее практичным, чем даже существующее С++ API.

Немного о gRPC и protobuf


Давайте смоделируем ситуацию: Вы собираетесь писать мультиплатформенный проект и Вам нужно выбрать RPC фрейморк для общения с Вашими сервисами. Можно всегда ударить себя кулаком в грудь и сказать «Я сам себе фреймфорк», но мне кажется мы живем в эру готовых решений. Одно из таких решений достаточно давно преподнесла нам одна известная компания. Я не берусь сравнивать RPC фреймворки, это не является целью данной статьи. Просто перечислю, что мне нравится в gRPC:

  • Лаконичный и понятный IDL
  • Наличие большого количества генераторов для различных платформ
  • Сгенерированные клиентский/серверный код, для быстрого и удобного прототипирования и написания тестовых приложений

К сути


Ввиду того, что у Qt все итак неплохо с рефлексией типов, а количество метаинформации так и вообще на высшем уровне, пришло осознание, что необходим свой генератор, который генерировал бы «чистейший» Qt код, без вкраплений сторонних библиотек. Так родился qtprotobufgen.

qtprotobufgen


qtprotobufgen это простейший по своей сути генератор, который основан на API предоставленном libprotoc. На случай если вы захотите смастерить, что-то подобное для своих нужд оставлю небольшой чит-шит.

  • У вас есть единая точка входа в плагин класс ::google::protobuf::compiler::CodeGenerator, от которого нужно отнаследоваться
  • Виртуальный метод Generate определяет генерацию при работе с отдельным .proto файлом
  • Виртуальный метод GenerateAll определяет генерацию при работе с полным скопом .proto файлов предоставленых для генерации либо являющихся зависимостями
  • Виртуальный метод HasGenerateAll по сути пережиток сохранившийся от прошлых версий. Верните true

Сразу оговорюсь, что не было никакого желания писать свой собственный парсер/генератор с нуля, поскольку есть готовое решение от разработчиков protobuf. Но при желании можно читать бинарный поток который выдает protoc, либо написать свой собственный парсер proto-файлов.

Во время разработки всплыл один существенный недостаток генератора написанного на компилируемом языке: было сложно уложить генерацию и компиляцию в один стэк CMake. Ввиду того, что Qt делает генерацию метаобъектной-информации, на основе заголовочных файлов имеющих макрос Q_OBJECT в теле классов объявленных в заголовочном файле, необходимо на этапе конфигурирования (читай cmake) иметь представление о файлах, которые будут предоставлены moc для дальнейшей генерации кода. В качестве решения пришлось прибегнуть к интерпретируемому языку Go(Lang), который не создал дополнительных зависимостей и прекрасно справился со своей задачей, но не прошел достаточного тестирования.

Генератор подчиняется существующим правилам protoc, и на момент написания статьи не вносит никаких дополнительных опций генерации:

protoc --plugin=protoc-gen-qtprotobuf=<path/to/bin>/qtprotobufgen --qtprotobuf_out=<output_dir> <protofile>.proto [--qtprotobuf_opt=out=<output_dir>]

Для простоты и удобства использования, можно использовать специально подготовленные рутины cmake, для генерации кода, и встроить их вызов в cmake проект. Подробнее...

О библиотеках


Не вижу большого смысла подробно описывать API. Желающие могут сгенерировать документацию и почитать чуть подробнее об имеющемся на данный момент API.

Проект поделен на 2 логические части qtprotobuf и qtgrpc. Из названий я думаю понятно назначение каждой компоненты. Мы постарались сделать использование максимально удобным, потому есть варианты интеграции как с предсобранной и установленной в системе библиотекой, так и интеграция подпроекта в ваш cmake проект.

Сгенерированный код полностью* экспортируется в QML, что делает работу с gRPC API значительно проще.

Использование


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

...
#include <QtProtobufTypes>
...
int main(int argc, char *argv[])
{
    QtProtobuf::registerProtoTypes();
    ... //Инициализация и запуск Qt приложения
}

На момент написания статьи, нет единого метода для регистрации всех типов сгенерированных для proto-пакета, поэтому необходимо вызвать метод qRegisterProtobufType для всех типов используемых в приложении:

...
qRegisterProtobufType<MyProtoType>();
...

Использование библиотек и генератора описано в README, а также проект сопровождает пара примеров. Для тех кто совсем не знаком с gRPC/protobuf предлагаю ознакомится с официальной документацией

Для разработчиков


Мы старались придерживаться TDD во время разработки, и не хотим отступать от него. Как показал наш опыт, TDD спасает при рефакторинге либо обновлении API, помогает обнаружить скрытые проблемы. Поэтому если появится желание контрибьютить, будте готовы к написанию юнит, модульных и функциональных тестов.

*Известные проблемы


В данный момент есть ряд проблем связанных с Qt. Часть из них были решены, с нашим либо без нашего участия, но далеко не все вошли в текущие релизы Qt. Основная — недоступность некоторых базовых типов protobuf из qml кода. Думаю ни для кого не секрет, что набор типов доступных в QML весьма ограничен, от части из-за использования V8 в качестве JS движка. Попытка сделать QML чуть более дружелюбным к кастомизированным типам (например fixed32, sint32) не удалась, но получилось исправить источник проблемы. Текущая имплементация QtNetwork также имеет ряд проблем, но команда Qt оперативно их исправляет.
QTBUG-77852
QTBUG-76303
QTBUG-78310

Планы


Все текущие активности связаны с устранением проблем в коде проекта либо в коде Qt. Но имеется достаточно большой скоп работ, связанный с новой функциональностью:

  1. Переход к единой паре .h/.cpp файлов для сгенерированного кода
  2. Имплементация серверной части gRPC
  3. Переработка API для gRPC credentials
  4. Распределение сгенерированного кода по директориям и создание плагинов-подпроектов для раздельной подгрузки сгенерированных пакетов и модулей
  5. Интеграция с qmake
  6. Внедрение CI

Есть некоторый бэклог, который пока хранится в собственном проектном репозитории.

Вместо заключения хотелось бы сказать спасибо товарищам из PVS-Studio, за предоставленный ключ для OSS проектов. С их помощью нашли достаточно критичный баг в сгенерированном коде.

Скачать, посмотреть проект и поиграться с примерами можно тут.
Tags:qt5qtgrpcprotobufapiopensourсe
Hubs: Qt API
+8
2.7k 28
Leave a comment