Padrões de projeto Protobuf


19

Estou avaliando o Google Protocol Buffers para um serviço baseado em Java (mas estou esperando padrões agnósticos de linguagem). Eu tenho duas perguntas:

A primeira é uma ampla questão geral:

Que padrões estamos vendo as pessoas usarem? Os referidos padrões estão relacionados à organização da classe (por exemplo, mensagens por arquivo .proto, empacotamento e distribuição) e definição de mensagem (por exemplo, campos repetidos vs. campos encapsulados repetidos *) etc.

Há muito poucas informações desse tipo nas páginas de Ajuda e nos blogs públicos do Google Protobuf, enquanto há uma grande quantidade de informações para protocolos estabelecidos, como XML.

Também tenho perguntas específicas sobre os dois padrões diferentes a seguir:

  1. Representar mensagens em arquivos .proto, empacotá-las como um frasco separado e enviá-las para os consumidores do serviço - que é basicamente a abordagem padrão, eu acho.

  2. Faça o mesmo, mas também inclua wrappers criados à mão (não subclasses!) Em torno de cada mensagem que implementa um contrato que suporte pelo menos esses dois métodos (T é a classe do wrapper, V é a classe da mensagem (usando genéricos, mas com sintaxe simplificada por questões de concisão) :

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

Uma vantagem que vejo em (2) é que posso ocultar as complexidades introduzidas por V.newBuilder().addField().build()e adicionar alguns métodos significativos, como isOpenForTrade()ou isAddressInFreeDeliveryZone()etc., nos meus wrappers. A segunda vantagem que vejo com (2) é que meus clientes lidam com objetos imutáveis ​​(algo que posso impor na classe wrapper).

Uma desvantagem que vejo em (2) é que duplico o código e tenho que sincronizar minhas classes de wrapper com arquivos .proto.

Alguém tem melhores técnicas ou críticas adicionais sobre qualquer uma das duas abordagens?


* Ao encapsular um campo repetido, quero dizer mensagens como esta:

message ItemList {
    repeated item = 1;
}

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

em vez de mensagens como esta:

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

Gosto do último, mas fico feliz em ouvir argumentos contra.


Preciso de mais 12 pontos para criar novas tags e acho que protobuf deve ser uma tag para este post.
Apoorv Khurasia

Respostas:


13

Onde eu trabalho, foi tomada a decisão de ocultar o uso de protobuf. Não distribuímos os .protoarquivos entre aplicativos, mas qualquer aplicativo que exponha uma interface protobuf exporta uma biblioteca cliente que possa conversar com ele.

Eu trabalhei apenas em um desses aplicativos de exposição a protobuf, mas, nesse caso, cada mensagem protobuf corresponde a algum conceito no domínio. Para cada conceito, existe uma interface Java normal. Existe então uma classe de conversor, que pode pegar uma instância de uma implementação e construir um objeto de mensagem apropriado e pegar um objeto de mensagem e construir uma instância de uma implementação da interface (por acaso, geralmente uma classe anônima ou local simples definida dentro do conversor). As classes de mensagem e conversores gerados por protobufs juntos formam uma biblioteca que é usada pelo aplicativo e pela biblioteca cliente; a biblioteca do cliente adiciona uma pequena quantidade de código para configurar conexões e enviar e receber mensagens.

Os aplicativos clientes importam a biblioteca do cliente e fornecem implementações de quaisquer interfaces que desejam enviar. De fato, ambos os lados fazem a última coisa.

Para esclarecer, isso significa que, se você tiver um ciclo de solicitação-resposta em que o cliente está enviando um convite para uma festa e o servidor está respondendo com uma RSVP, as coisas envolvidas são:

  • Mensagem PartyInvitation, escrita no .protoarquivo
  • PartyInvitationMessage classe, gerada por protoc
  • PartyInvitation interface, definida na biblioteca compartilhada
  • ActualPartyInvitation, uma implementação concreta da PartyInvitationdefinida pelo aplicativo cliente (na verdade não é chamada assim!)
  • StubPartyInvitation, uma implementação simples de PartyInvitationdefinida pela biblioteca compartilhada
  • PartyInvitationConverter, que pode converter um PartyInvitationpara um PartyInvitationMessagee um PartyInvitationMessagepara umStubPartyInvitation
  • Mensagem RSVP, escrita no .protoarquivo
  • RSVPMessage classe, gerada por protoc
  • RSVP interface, definida na biblioteca compartilhada
  • ActualRSVP, uma implementação concreta da RSVPdefinida pelo aplicativo do servidor (também não chamada de verdade!)
  • StubRSVP, uma implementação simples de RSVPdefinida pela biblioteca compartilhada
  • RSVPConverter, que pode converter um RSVPpara um RSVPMessagee um RSVPMessagepara umStubRSVP

A razão pela qual temos implementações reais e stub separadas é que as implementações reais geralmente são classes de entidades mapeadas pela JPA; o servidor as cria e persiste ou as consulta no banco de dados e as entrega para a camada protobuf a ser transmitida. Não era apropriado criar instâncias dessas classes no lado receptor da conexão, porque elas não seriam vinculadas a um contexto de persistência. Além disso, as entidades geralmente contêm mais dados do que os transmitidos por fio, portanto, nem seria possível criar objetos intactos no lado receptor. Não estou inteiramente convencido de que esse foi o passo certo, porque nos deixou mais uma aula por mensagem do que de outra forma teríamos.

Na verdade, não estou inteiramente convencido de que usar protobuf seja uma boa ideia; se tivéssemos ficado com RMI e serialização simples, não precisaríamos criar quase tantos objetos. Em muitos casos, poderíamos simplesmente ter marcado nossas classes de entidade como serializáveis ​​e continuar com isso.

Agora, tendo dito tudo isso, tenho um amigo que trabalha no Google, em uma base de código que faz uso pesado de protobuf para comunicação entre módulos. Eles adotam uma abordagem completamente diferente: não envolvem as classes de mensagens geradas e as transmitem com entusiasmo (ish) em seu código. Isso é visto como uma coisa boa, porque é uma maneira simples de manter as interfaces flexíveis. Não há código de andaime para manter a sincronização quando as mensagens evoluem, e as classes geradas fornecem todos os hasFoo()métodos necessários para receber código para detectar a presença ou ausência de campos que foram adicionados ao longo do tempo. Porém, lembre-se de que as pessoas que trabalham no Google tendem a ser (a) bastante inteligentes e (b) um pouco loucas.


A certa altura, examinei o uso da serialização do JBoss como um substituto mais ou menos automático para a serialização padrão. Foi bem mais rápido. Não é tão rápido quanto o protobuf.
Tom Anderson

A serialização JSON usando jackson2 também é bastante rápida. O que eu odeio no GBP é a duplicação desnecessária das principais classes de interface.
Apoorv Khurasia

0

Para adicionar a resposta de Anderson, há uma linha tênue em aninhar mensagens de maneira inteligente uma na outra e exagerar. O problema é que cada mensagem cria uma nova classe nos bastidores e todos os tipos de acessadores e manipuladores para os dados. Mas há um custo para isso se você precisar copiar os dados ou alterar um valor ou comparar as mensagens. Esses processos podem ser muito lentos e dolorosos, se você tiver muitos dados ou estiver limitado pelo tempo.


2
isso se parece mais com um comentário tangencial, consulte Como responder
gnat

1
Bem que não é: não há domínios existem classes é uma questão toda redação no final (oh Estou desenvolvendo todas as minhas coisas em C ++, mas isso deve nenhuma abelha um problema)
Marko Bencik
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.