Protobuf шаблоны проектирования

19

Я оцениваю буферы протокола Google для службы на основе Java (но ожидаю языковых шаблонов). У меня есть два вопроса:

Первый общий вопрос:

Какие образцы мы видим, люди используют? Указанные шаблоны связаны с организацией класса (например, сообщения на файл .proto, упаковку и распространение) и определением сообщения (например, повторяющиеся поля и повторяющиеся инкапсулированные поля *) и т. Д.

Подобной информации очень мало на страницах справки Google Protobuf и в общедоступных блогах, в то время как существует масса информации для установленных протоколов, таких как XML.

У меня также есть конкретные вопросы по следующим двум различным схемам:

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

  2. Сделайте то же самое, но также включайте созданные вручную обертки (не подклассы!) Вокруг каждого сообщения, которое реализует контракт, поддерживающий, по крайней мере, эти два метода (T - класс обертки, V - класс сообщения (с использованием обобщений, но упрощенного синтаксиса для краткости) :

    public V toProtobufMessage() {
        V.Builder builder = V.newBuilder();
        for (Item item : getItemList()) {
            builder.addItem(item);
        }
        return builder.setAmountPayable(getAmountPayable()).
                       setShippingAddress(getShippingAddress()).
                       build();
    }
    
    public static T fromProtobufMessage(V message_) { 
        return new T(message_.getShippingAddress(), 
                     message_.getItemList(),
                     message_.getAmountPayable());
    }
    

Одно преимущество, которое я вижу с помощью (2), заключается в том, что я могу скрыть сложности, возникающие при V.newBuilder().addField().build()добавлении, и добавить некоторые осмысленные методы, такие как isOpenForTrade()или isAddressInFreeDeliveryZone()другие, в свои оболочки. Второе преимущество, которое я вижу в (2), заключается в том, что мои клиенты имеют дело с неизменяемыми объектами (что я могу применить в классе-обертке).

Один недостаток, который я вижу в (2), заключается в том, что я дублирую код и должен синхронизировать мои классы-обертки с файлами .proto.

У кого-нибудь есть лучшие методы или дополнительная критика по любому из двух подходов?


* Под инкапсуляцией повторяющегося поля я имею в виду такие сообщения:

message ItemList {
    repeated item = 1;
}

message CustomerInvoice {
    required ShippingAddress address = 1;
    required ItemList = 2;
    required double amountPayable = 3;
}

вместо сообщений типа этого:

message CustomerInvoice {
    required ShippingAddress address = 1;
    repeated Item item = 2;
    required double amountPayable = 3;
}

Мне нравится последнее, но я рад услышать аргументы против этого.

Апурв Хурасия
источник
Мне нужно еще 12 очков для создания новых тегов, и я думаю, что protobuf должен быть тегом для этого поста.
Апурв Хурасия

Ответы:

13

Там, где я работаю, было принято решение скрыть использование protobuf. Мы не распределяем .protoфайлы между приложениями, а скорее, любое приложение, предоставляющее интерфейс protobuf, экспортирует клиентскую библиотеку, которая может с ней общаться.

Я работал только над одним из этих приложений, предоставляющих протобуф, но в этом каждое сообщение о протобуфе соответствует некоторой концепции в домене. Для каждой концепции существует нормальный интерфейс Java. Затем существует класс преобразователя, который может взять экземпляр реализации и создать соответствующий объект сообщения, а также взять объект сообщения и создать экземпляр реализации интерфейса (как это обычно бывает, определяется простой анонимный или локальный класс). внутри конвертера). Генерируемые протобуфом классы сообщений и конвертеры вместе образуют библиотеку, которая используется приложением и клиентской библиотекой; клиентская библиотека добавляет небольшой объем кода для настройки соединений, отправки и получения сообщений.

Затем клиентские приложения импортируют клиентскую библиотеку и предоставляют реализации любых интерфейсов, которые они хотят отправить. Действительно, обе стороны делают последнее.

Чтобы уточнить, это означает, что если у вас есть цикл запрос-ответ, когда клиент отправляет приглашение на вечеринку, а сервер отвечает с помощью RSVP, то задействуются следующие вещи:

  • Сообщение PartyInvitation, записанное в .protoфайле
  • PartyInvitationMessage класс, сгенерированный protoc
  • PartyInvitation интерфейс, определенный в общей библиотеке
  • ActualPartyInvitationконкретная реализация, PartyInvitationопределенная клиентским приложением (на самом деле это не называется!)
  • StubPartyInvitationпростая реализация, PartyInvitationопределяемая разделяемой библиотекой
  • PartyInvitationConverter, который может преобразовать PartyInvitationв а PartyInvitationMessage, а PartyInvitationMessageвStubPartyInvitation
  • Сообщение RSVP, записанное в .protoфайле
  • RSVPMessage класс, сгенерированный protoc
  • RSVP интерфейс, определенный в общей библиотеке
  • ActualRSVPконкретная реализация, RSVPопределенная серверным приложением (также не называется так!)
  • StubRSVPпростая реализация, RSVPопределяемая разделяемой библиотекой
  • RSVPConverter, который может преобразовать RSVPв RSVPMessage, и RSVPMessageвStubRSVP

Причина, по которой у нас есть отдельные фактические реализации и реализации-заглушки, заключается в том, что фактические реализации, как правило, являются классами сущностей, отображаемыми в JPA; сервер либо создает и сохраняет их, либо запрашивает их из базы данных, а затем передает их на уровень protobuf для передачи. Не считалось целесообразным создавать экземпляры этих классов на принимающей стороне соединения, потому что они не были бы привязаны к контексту постоянства. Кроме того, объекты часто содержат гораздо больше данных, чем передается по проводам, поэтому даже на целевом объекте невозможно было бы создать целые объекты. Я не совсем уверен, что это был правильный шаг, потому что он оставил нам на один класс больше, чем мы могли бы иметь.

На самом деле, я не совсем убежден, что использование protobuf вообще было хорошей идеей; если бы мы застряли с простым старым RMI и сериализацией, нам не пришлось бы создавать почти столько же объектов. Во многих случаях мы могли просто пометить наши классы сущностей как сериализуемые и покончить с этим.

Теперь, после всего сказанного, у меня есть друг, который работает в Google, на базе кода, которая интенсивно использует protobuf для связи между модулями. Они используют совершенно другой подход: они вообще не оборачивают сгенерированные классы сообщений и с энтузиазмом передают их глубоко (иш) в свой код. Это рассматривается как хорошая вещь, потому что это простой способ сохранить гибкость интерфейсов. Нет кода для поддержки синхронизации, когда сообщения эволюционируют, и сгенерированные классы предоставляют все необходимые hasFoo()методы, необходимые для получения кода, чтобы обнаружить наличие или отсутствие полей, которые были добавлены с течением времени. Имейте в виду, однако, что люди, которые работают в Google, как правило, (а) довольно умны и (б) немного сумасшедшие.

Том Андерсон
источник
В какой-то момент я решил использовать сериализацию JBoss в качестве более или менее подходящей замены стандартной сериализации. Это было намного быстрее. Но не так быстро, как протобуф.
Том Андерсон
Сериализация JSON с использованием jackson2 тоже довольно быстрая. Что я ненавижу в GBP, так это ненужное дублирование основных классов интерфейса.
Апурв Хурасия
0

Чтобы добавить ответ Андерсона, есть тонкая грань в том, что сообщения нужно ловко вкладывать друг в друга и переусердствовать. Проблема в том, что каждое сообщение создает новый класс за кулисами и всевозможные методы доступа и обработки данных. Но это стоит того, чтобы скопировать данные, изменить одно значение или сравнить сообщения. Эти процессы могут быть очень медленными и болезненными, если у вас много данных или время ограничено.

Марко Бенчик
источник
2
это больше похоже на тангенциальный комментарий, см. Как ответить
Гнат
1
Ну, это не так: нет доменов, есть классы. В конце концов, все проблемы с формулировками (о, я разрабатываю все свои вещи на C ++, но это не должно быть проблемой)
Марко Бенчик