Я оцениваю буферы протокола Google для службы на основе Java (но ожидаю языковых шаблонов). У меня есть два вопроса:
Первый общий вопрос:
Какие образцы мы видим, люди используют? Указанные шаблоны связаны с организацией класса (например, сообщения на файл .proto, упаковку и распространение) и определением сообщения (например, повторяющиеся поля и повторяющиеся инкапсулированные поля *) и т. Д.
Подобной информации очень мало на страницах справки Google Protobuf и в общедоступных блогах, в то время как существует масса информации для установленных протоколов, таких как XML.
У меня также есть конкретные вопросы по следующим двум различным схемам:
Представлять сообщения в файлах .proto, упаковывать их в отдельный jar-файл и отправлять его целевым пользователям сервиса, что, по-моему, является подходом по умолчанию.
Сделайте то же самое, но также включайте созданные вручную обертки (не подклассы!) Вокруг каждого сообщения, которое реализует контракт, поддерживающий, по крайней мере, эти два метода (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;
}
Мне нравится последнее, но я рад услышать аргументы против этого.
источник
Ответы:
Там, где я работаю, было принято решение скрыть использование protobuf. Мы не распределяем
.proto
файлы между приложениями, а скорее, любое приложение, предоставляющее интерфейс protobuf, экспортирует клиентскую библиотеку, которая может с ней общаться.Я работал только над одним из этих приложений, предоставляющих протобуф, но в этом каждое сообщение о протобуфе соответствует некоторой концепции в домене. Для каждой концепции существует нормальный интерфейс Java. Затем существует класс преобразователя, который может взять экземпляр реализации и создать соответствующий объект сообщения, а также взять объект сообщения и создать экземпляр реализации интерфейса (как это обычно бывает, определяется простой анонимный или локальный класс). внутри конвертера). Генерируемые протобуфом классы сообщений и конвертеры вместе образуют библиотеку, которая используется приложением и клиентской библиотекой; клиентская библиотека добавляет небольшой объем кода для настройки соединений, отправки и получения сообщений.
Затем клиентские приложения импортируют клиентскую библиотеку и предоставляют реализации любых интерфейсов, которые они хотят отправить. Действительно, обе стороны делают последнее.
Чтобы уточнить, это означает, что если у вас есть цикл запрос-ответ, когда клиент отправляет приглашение на вечеринку, а сервер отвечает с помощью RSVP, то задействуются следующие вещи:
.proto
файлеPartyInvitationMessage
класс, сгенерированныйprotoc
PartyInvitation
интерфейс, определенный в общей библиотекеActualPartyInvitation
конкретная реализация,PartyInvitation
определенная клиентским приложением (на самом деле это не называется!)StubPartyInvitation
простая реализация,PartyInvitation
определяемая разделяемой библиотекойPartyInvitationConverter
, который может преобразоватьPartyInvitation
в аPartyInvitationMessage
, аPartyInvitationMessage
вStubPartyInvitation
.proto
файлеRSVPMessage
класс, сгенерированныйprotoc
RSVP
интерфейс, определенный в общей библиотекеActualRSVP
конкретная реализация,RSVP
определенная серверным приложением (также не называется так!)StubRSVP
простая реализация,RSVP
определяемая разделяемой библиотекойRSVPConverter
, который может преобразоватьRSVP
вRSVPMessage
, иRSVPMessage
вStubRSVP
Причина, по которой у нас есть отдельные фактические реализации и реализации-заглушки, заключается в том, что фактические реализации, как правило, являются классами сущностей, отображаемыми в JPA; сервер либо создает и сохраняет их, либо запрашивает их из базы данных, а затем передает их на уровень protobuf для передачи. Не считалось целесообразным создавать экземпляры этих классов на принимающей стороне соединения, потому что они не были бы привязаны к контексту постоянства. Кроме того, объекты часто содержат гораздо больше данных, чем передается по проводам, поэтому даже на целевом объекте невозможно было бы создать целые объекты. Я не совсем уверен, что это был правильный шаг, потому что он оставил нам на один класс больше, чем мы могли бы иметь.
На самом деле, я не совсем убежден, что использование protobuf вообще было хорошей идеей; если бы мы застряли с простым старым RMI и сериализацией, нам не пришлось бы создавать почти столько же объектов. Во многих случаях мы могли просто пометить наши классы сущностей как сериализуемые и покончить с этим.
Теперь, после всего сказанного, у меня есть друг, который работает в Google, на базе кода, которая интенсивно использует protobuf для связи между модулями. Они используют совершенно другой подход: они вообще не оборачивают сгенерированные классы сообщений и с энтузиазмом передают их глубоко (иш) в свой код. Это рассматривается как хорошая вещь, потому что это простой способ сохранить гибкость интерфейсов. Нет кода для поддержки синхронизации, когда сообщения эволюционируют, и сгенерированные классы предоставляют все необходимые
hasFoo()
методы, необходимые для получения кода, чтобы обнаружить наличие или отсутствие полей, которые были добавлены с течением времени. Имейте в виду, однако, что люди, которые работают в Google, как правило, (а) довольно умны и (б) немного сумасшедшие.источник
Чтобы добавить ответ Андерсона, есть тонкая грань в том, что сообщения нужно ловко вкладывать друг в друга и переусердствовать. Проблема в том, что каждое сообщение создает новый класс за кулисами и всевозможные методы доступа и обработки данных. Но это стоит того, чтобы скопировать данные, изменить одно значение или сравнить сообщения. Эти процессы могут быть очень медленными и болезненными, если у вас много данных или время ограничено.
источник