Шаблони дизайну протобуфа


19

Я оцінюю буфери протоколів Google для служби на базі Java (але очікую мовних агностичних моделей). У мене є два питання:

Перше - широке загальне питання:

Які моделі ми бачимо, як люди використовують? Зазначені зразки пов'язані з організацією класу (наприклад, повідомлення за файлом .proto, упаковкою та розповсюдженням) та визначенням повідомлення (наприклад, повторювані поля проти повторних інкапсульованих полів *) тощо.

На сторінках довідки Google Protobuf та загальнодоступних блогах є дуже мало такої інформації, тоді як є багато інформації для встановлених протоколів, таких як XML.

У мене також є конкретні запитання щодо наступних двох різних моделей:

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

  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 пунктів, щоб створити нові теги, і я думаю, що протобуф повинен бути тегом для цієї публікації.
Апоорв Хурасія

Відповіді:


13

Де я працюю, було прийнято рішення про приховування використання протобуфа. Ми не поширюємо .protoфайли між додатками, а, скоріше, будь-яка програма, яка відкриває протобуфський інтерфейс, експортує клієнтську бібліотеку, яка може з нею спілкуватися.

Я працював лише над одним із цих додатків, що відкривають протобуфи, але в цьому кожне повідомлення протобуфа відповідає певній концепції в області. Для кожної концепції існує звичайний інтерфейс Java. Потім існує клас перетворювача, який може приймати екземпляр реалізації та побудувати відповідний об'єкт повідомлення, а також взяти об’єкт повідомлення та побудувати екземпляр реалізації інтерфейсу (як це буває, як правило, визначений простий анонімний або локальний клас всередині перетворювача). Класи і перетворювачі, створені протобуфами, разом утворюють бібліотеку, яка використовується як додатком, так і клієнтською бібліотекою; клієнтська бібліотека додає невелику кількість коду для налаштування з'єднань та надсилання та прийому повідомлень.

Потім клієнтські програми імпортують клієнтську бібліотеку та надають реалізацію будь-яких інтерфейсів, які вони хочуть надіслати. Дійсно, обидві сторони роблять останнє.

Щоб уточнити, це означає, що якщо у вас є цикл відповіді на запит, коли клієнт надсилає запрошення на вечірку, а сервер відповідає з RSVP, то це стосується:

  • Повідомлення PartyInvitation, записане у .protoфайлі
  • PartyInvitationMessage клас, породжений користувачем protoc
  • PartyInvitation інтерфейс, визначений у спільній бібліотеці
  • ActualPartyInvitation, конкретна реалізація PartyInvitationвизначеної клієнтською програмою (насправді не називається так!)
  • StubPartyInvitation, проста реалізація, PartyInvitationвизначена спільною бібліотекою
  • PartyInvitationConverter, який може перетворити a PartyInvitationв a PartyInvitationMessageі a PartyInvitationMessageв aStubPartyInvitation
  • Повідомлення RSVP, записане у .protoфайл
  • RSVPMessage клас, породжений користувачем protoc
  • RSVP інтерфейс, визначений у спільній бібліотеці
  • ActualRSVP, конкретна реалізація RSVPвизначеної серверною програмою (також насправді так не називається!)
  • StubRSVP, проста реалізація, RSVPвизначена спільною бібліотекою
  • RSVPConverter, який може перетворити RSVPна а RSVPMessage, а RSVPMessageв аStubRSVP

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

Дійсно, я не зовсім переконаний, що використання протобуфа взагалі було гарною ідеєю; якби ми дотримувались простої старої RMI та серіалізації, нам не довелося б створювати майже стільки об’єктів. У багатьох випадках ми могли просто визначити класи нашого навчального закладу як серіалізаційні та продовжувати його працювати.

Тепер, сказавши все це, у мене є друг, який працює в Google, по кодовій базі, який широко використовує протобуф для зв'язку між модулями. Вони застосовують зовсім інший підхід: вони взагалі не переносять створені класи повідомлень і з ентузіазмом передають їх у свій код. Це сприймається як хороша річ, оскільки це простий спосіб збереження гнучкості інтерфейсів. Не існує коду лісу для синхронізації, коли повідомлення еволюціонують, а згенеровані класи надають усі необхідні hasFoo()методи, необхідні для отримання коду для виявлення наявності чи відсутності полів, доданих із часом. Майте на увазі, що люди, які працюють в Google, як правило, (а) досить розумні і (б) трохи горіхи.


Одного разу я розглядав можливість використання серіалізації JBoss як більш-менш замінника для стандартної серіалізації. Це було набагато швидше. Хоча не так швидко, як протобуф.
Том Андерсон

Серіалізація JSON за допомогою jackson2 теж досить швидко. Я ненавиджу GBP - це непотрібне дублювання основних класів інтерфейсу.
Апоорв Хурасія

0

Щоб додати відповідь Андерсона, існує чітка лінія в гніздовому повідомленні, яке вміло вкладається одне в інше і перестарається. Проблема полягає в тому, що кожне повідомлення створює новий клас поза кадром і всілякі аксесуари та обробники даних. Але це коштує, якщо вам доведеться скопіювати дані або змінити одне значення або порівняти повідомлення. Ці процеси можуть бути дуже повільними і болісними, якщо ви маєте багато даних або обмежені часом.


2
це більше нагадує дотичний коментар, див. Як відповісти
gnat

1
Ну це не так: немає доменів, є класи, все це формулювання зрештою (о, я розвиваю всі свої речі в C ++, але це не повинно бути проблемою бджоли)
Marko Bencik
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.