Wzory projektowe Protobuf


19

Oceniam bufory protokołu Google dla usługi opartej na Javie (ale oczekuję wzorców agnostycznych języka). Mam dwa pytania:

Pierwsze to ogólne pytanie ogólne:

Jakie wzory widzimy, ludzie używają? Wspomniane wzorce są powiązane z organizacją klas (np. Wiadomości na plik .proto, pakowanie i dystrybucja) i definicją wiadomości (np. Pola powtarzane vs. pola powtarzane *) itp.

Na stronach pomocy Google Protobuf i blogach publicznych jest bardzo mało tego rodzaju informacji, podczas gdy istnieje mnóstwo informacji o ustalonych protokołach, takich jak XML.

Mam również konkretne pytania dotyczące następujących dwóch różnych wzorów:

  1. Reprezentuj wiadomości w plikach .proto, pakuj je jako osobny słoik i wysyłaj do docelowych klientów usługi - co jest w zasadzie domyślnym podejściem.

  2. Zrób to samo, ale dołącz również ręcznie wykonane opakowania (nie podklasy!) Wokół każdej wiadomości, która implementuje kontrakt obsługujący co najmniej te dwie metody (T to klasa opakowania, V to klasa wiadomości (używająca ogólnych, ale uproszczonej składni dla zwięzłości) :

    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());
    }
    

Jedną z zalet, które widzę w (2), jest to, że mogę ukryć złożoność wprowadzoną przez V.newBuilder().addField().build()i dodać kilka znaczących metod, takich jak isOpenForTrade()lub isAddressInFreeDeliveryZone()itp. W moich opakowaniach. Drugą zaletą, którą widzę w (2), jest to, że moi klienci radzą sobie z niezmiennymi obiektami (coś, co mogę wymusić w klasie opakowania).

Jedną wadą, którą widzę w (2), jest to, że duplikuję kod i muszę zsynchronizować swoje klasy opakowań z plikami .proto.

Czy ktoś ma lepsze techniki lub dalsze krytyki któregokolwiek z tych dwóch podejść?


* Przez enkapsulację powtarzającego się pola mam na myśli takie wiadomości jak ten:

message ItemList {
    repeated item = 1;
}

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

zamiast wiadomości takich jak ten:

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

Podoba mi się to drugie, ale cieszę się, że mogę wysłuchać argumentów przeciwko niemu.


Potrzebuję jeszcze 12 punktów, aby utworzyć nowe tagi i myślę, że protobuf powinien być tagiem dla tego postu.
Apoorv Khurasia

Odpowiedzi:


13

Tam, gdzie pracuję, postanowiono ukryć użycie protobufa. Nie dystrybuujemy .protoplików między aplikacjami, ale każda aplikacja, która udostępnia interfejs protobuf, eksportuje bibliotekę klienta, która może z nią rozmawiać.

Pracowałem tylko nad jedną z tych aplikacji ujawniających protobuf, ale pod tym względem każda wiadomość protobuf odpowiada pewnej koncepcji w tej dziedzinie. Dla każdej koncepcji istnieje normalny interfejs Java. Jest wtedy klasa konwertera, która może wziąć instancję implementacji i zbudować odpowiedni obiekt komunikatu, a także wziąć obiekt komunikatu i zbudować instancję implementacji interfejsu (jak to się dzieje, zwykle jest to zwykła anonimowa lub lokalna klasa zdefiniowana wewnątrz konwertera). Generowane przez protobuf klasy komunikatów i konwertery razem tworzą bibliotekę, która jest używana zarówno przez aplikację, jak i bibliotekę klienta; biblioteka klienta dodaje niewielką ilość kodu do konfigurowania połączeń oraz wysyłania i odbierania wiadomości.

Następnie aplikacje klienckie importują bibliotekę klienta i zapewniają implementacje dowolnych interfejsów, które chcą wysłać. Rzeczywiście, obie strony robią to drugie.

Aby to wyjaśnić, oznacza to, że jeśli masz cykl żądanie-odpowiedź, w którym klient wysyła zaproszenie na imprezę, a serwer odpowiada przez RSVP, to są to następujące rzeczy:

  • Wiadomość PartyInvitation zapisana w .protopliku
  • PartyInvitationMessage klasa, wygenerowana przez protoc
  • PartyInvitation interfejs zdefiniowany we wspólnej bibliotece
  • ActualPartyInvitation, konkretna implementacja PartyInvitationzdefiniowana przez aplikację kliencką (tak naprawdę jej nie nazywa!)
  • StubPartyInvitation, prosta implementacja PartyInvitationzdefiniowana przez bibliotekę współdzieloną
  • PartyInvitationConverter, które mogą konwertować a PartyInvitationna a PartyInvitationMessage, i PartyInvitationMessageaStubPartyInvitation
  • Komunikat RSVP zapisany w .protopliku
  • RSVPMessage klasa, wygenerowana przez protoc
  • RSVP interfejs zdefiniowany we wspólnej bibliotece
  • ActualRSVP, konkretne wdrożenie RSVP zdefiniowana przez aplikację serwera (też tak naprawdę nie jest tak nazywana!)
  • StubRSVP, prosta implementacja RSVPzdefiniowana przez bibliotekę współdzieloną
  • RSVPConverter, które mogą konwertować an RSVPna an RSVPMessagei RSVPMessagena aStubRSVP

Powodem, dla którego mamy osobne implementacje rzeczywiste i pośrednie, jest fakt, że rzeczywiste implementacje są zazwyczaj klasami jednostek odwzorowanych na JPA; serwer albo je tworzy i utrwala, albo wysyła do nich zapytania z bazy danych, a następnie przekazuje je do warstwy protobuf, która ma zostać przesłana. Nie uważano za stosowne tworzenie instancji tych klas po stronie odbierającej połączenie, ponieważ nie byłyby one powiązane z kontekstem trwałości. Co więcej, byty często zawierają raczej więcej danych niż jest przesyłanych przewodowo, więc nie byłoby nawet możliwe stworzenie nienaruszonych obiektów po stronie odbiorczej. Nie jestem do końca przekonany, czy to był właściwy ruch, ponieważ pozostawił nam o jedną klasę więcej na wiadomość niż w przeciwnym razie.

Rzeczywiście, nie jestem do końca przekonany, że użycie protobuf w ogóle było dobrym pomysłem; gdybyśmy trzymali się zwykłego starego RMI i serializacji, nie musielibyśmy tworzyć prawie tylu obiektów. W wielu przypadkach moglibyśmy po prostu oznaczyć nasze klasy bytów jako możliwe do serializacji i zająć się tym.

Teraz, powiedziawszy to wszystko, mam znajomego, który pracuje w Google, na bazie kodu, która intensywnie wykorzystuje protobuf do komunikacji między modułami. Przyjmują zupełnie inne podejście: nie zawijają generowanych klas komunikatów i entuzjastycznie przekazują je głęboko (ish) do swojego kodu. Jest to postrzegane jako dobra rzecz, ponieważ jest to prosty sposób na zachowanie elastyczności interfejsów. Nie ma kodu rusztowania, który byłby zsynchronizowany, gdy wiadomości ewoluują, a wygenerowane klasy zapewniają wszystkie niezbędne hasFoo()metody potrzebne do odbierania kodu w celu wykrycia obecności lub braku pól, które zostały dodane z czasem. Pamiętaj jednak, że ludzie pracujący w Google są (a) raczej sprytni i (b) trochę szaleni.


W pewnym momencie zastanowiłem się nad użyciem serializacji JBoss jako mniej lub bardziej zastępczego standardowego serializacji. Było znacznie szybciej. Jednak nie tak szybki jak protobuf.
Tom Anderson

Serializacja JSON przy użyciu jackson2 jest również dość szybka. Rzeczą, której nienawidzę w GBP, jest niepotrzebne powielanie głównych klas interfejsów.
Apoorv Khurasia

0

Aby dodać odpowiedź Andersona, istnieje cienka linia sprytnego zagnieżdżania wiadomości i przesadzania. Problem polega na tym, że każda wiadomość tworzy za kulisami nową klasę oraz różnego rodzaju akcesoria i moduły obsługi danych. Ale wiąże się to z pewnym kosztem, jeśli musisz skopiować dane lub zmienić jedną wartość lub porównać wiadomości. Procesy te mogą być bardzo powolne i bolesne, jeśli masz dużo danych lub czas.


2
brzmi to bardziej jako komentarz styczny, patrz Jak odpowiedzieć
gnat

1
Cóż, to nie jest: nie ma domen, są klasy, na końcu jest to kwestia sformułowania (och, rozwijam wszystkie moje rzeczy w C ++, ale to nie może być problem pszczół)
Marko Bencik
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.