Pola desain Protobuf

19

Saya mengevaluasi Buffer Protokol Google untuk layanan berbasis Java (tetapi saya mengharapkan pola agnostik bahasa). Saya punya dua pertanyaan:

Yang pertama adalah pertanyaan umum yang luas:

Pola apa yang kita lihat digunakan orang? Pola tersebut terkait dengan organisasi kelas (mis., Pesan per file .proto, pengemasan, dan distribusi) dan definisi pesan (misalnya, bidang berulang vs. bidang terenkapsulasi berulang *) dll.

Ada sangat sedikit informasi semacam ini di halaman Bantuan Google Protobuf dan blog publik sementara ada banyak informasi untuk protokol yang dibuat seperti XML.

Saya juga memiliki pertanyaan spesifik atas dua pola berikut:

  1. Mewakili pesan dalam file .proto, mengemasnya sebagai toples terpisah, dan mengirimkannya untuk menargetkan konsumen layanan - yang pada dasarnya adalah pendekatan standar yang saya kira.

  2. Lakukan hal yang sama tetapi juga sertakan pembungkus kerajinan tangan (bukan sub-kelas!) Di sekitar setiap pesan yang menerapkan kontrak yang mendukung setidaknya dua metode ini (T adalah kelas pembungkus, V adalah kelas pesan (menggunakan generik tetapi sintaksis yang disederhanakan untuk singkatnya) :

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

Satu keuntungan yang saya lihat dengan (2) adalah bahwa saya dapat menyembunyikan kerumitan yang diperkenalkan oleh V.newBuilder().addField().build()dan menambahkan beberapa metode yang berarti seperti isOpenForTrade()atau isAddressInFreeDeliveryZone()dll di pembungkus saya. Keuntungan kedua yang saya lihat (2) adalah bahwa klien saya berurusan dengan objek yang tidak dapat diubah (sesuatu yang dapat saya terapkan di kelas wrapper).

Satu kekurangan yang saya lihat dengan (2) adalah saya menduplikasi kode dan harus menyinkronkan kelas pembungkus saya dengan file .proto.

Adakah yang punya teknik yang lebih baik atau kritik lebih lanjut tentang salah satu dari dua pendekatan ini?


* Dengan merangkum bidang yang diulang, maksud saya pesan seperti ini:

message ItemList {
    repeated item = 1;
}

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

alih-alih pesan seperti ini:

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

Saya suka yang terakhir tetapi saya senang mendengar argumen menentangnya.

Apoorv Khurasia
sumber
Saya perlu 12 poin lagi untuk membuat tag baru dan saya pikir protobuf harus menjadi tag untuk posting ini.
Apoorv Khurasia

Jawaban:

13

Di mana saya bekerja, keputusan diambil untuk menyembunyikan penggunaan protobuf. Kami tidak mendistribusikan .protofile antar aplikasi, melainkan aplikasi apa pun yang mengekspos antarmuka protobuf mengekspor perpustakaan klien yang dapat berbicara dengannya.

Saya hanya bekerja pada salah satu dari aplikasi pengekspos protobuf ini, tetapi dalam hal itu, setiap pesan protobuf sesuai dengan beberapa konsep dalam domain. Untuk setiap konsep, ada antarmuka Java normal. Kemudian ada kelas konverter, yang dapat mengambil instance dari implementasi dan membangun objek pesan yang sesuai, dan mengambil objek pesan dan membangun instance dari implementasi antarmuka (seperti yang terjadi, biasanya kelas anonim sederhana atau lokal yang ditentukan di dalam konverter). Kelas pesan dan konverter yang dihasilkan protobuf bersama-sama membentuk perpustakaan yang digunakan oleh aplikasi dan perpustakaan klien; perpustakaan klien menambahkan sejumlah kecil kode untuk mengatur koneksi dan mengirim dan menerima pesan.

Aplikasi klien kemudian mengimpor perpustakaan klien, dan menyediakan implementasi dari setiap antarmuka yang ingin mereka kirim. Memang, kedua belah pihak melakukan hal yang terakhir.

Untuk mengklarifikasi, itu berarti bahwa jika Anda memiliki siklus permintaan-respons di mana klien mengirim undangan pesta, dan server merespons dengan RSVP, maka hal-hal yang terlibat adalah:

  • Pesan PartyInvitation, ditulis dalam .protofile
  • PartyInvitationMessage kelas, dihasilkan oleh protoc
  • PartyInvitation antarmuka, didefinisikan di perpustakaan bersama
  • ActualPartyInvitation, implementasi konkret yang PartyInvitationditentukan oleh aplikasi klien (sebenarnya tidak disebut itu!)
  • StubPartyInvitation, implementasi sederhana yang PartyInvitationdidefinisikan oleh perpustakaan bersama
  • PartyInvitationConverter, yang dapat mengonversi a PartyInvitationke PartyInvitationMessage, dan PartyInvitationMessagekeStubPartyInvitation
  • Pesan RSVP, ditulis dalam .protofile
  • RSVPMessage kelas, dihasilkan oleh protoc
  • RSVP antarmuka, didefinisikan di perpustakaan bersama
  • ActualRSVP, implementasi konkret yang RSVPditentukan oleh aplikasi server (juga tidak benar-benar menyebutnya!)
  • StubRSVP, implementasi sederhana yang RSVPdidefinisikan oleh perpustakaan bersama
  • RSVPConverter, yang dapat mengonversi sebuah RSVPke RSVPMessage, dan RSVPMessagekeStubRSVP

Alasan kami memiliki implementasi aktual dan rintisan yang terpisah adalah bahwa implementasi sebenarnya adalah kelas entitas yang dipetakan JPA; server dapat membuat dan bertahan, atau meminta mereka dari database, kemudian menyerahkannya ke lapisan protobuf untuk ditransmisikan. Rasanya tidak pantas membuat instance kelas-kelas tersebut di sisi penerima koneksi, karena mereka tidak akan terikat pada konteks kegigihan. Selain itu, entitas sering mengandung lebih banyak data daripada yang ditransmisikan melalui kabel, sehingga bahkan tidak mungkin untuk membuat objek utuh di sisi penerima. Saya tidak sepenuhnya yakin bahwa ini adalah langkah yang tepat, karena telah meninggalkan kita dengan satu kelas lebih dari pesan yang seharusnya kita miliki.

Memang, saya tidak sepenuhnya yakin bahwa menggunakan protobuf sama sekali adalah ide yang baik; jika kita terjebak dengan RMI dan serialisasi lama, kita tidak perlu membuat banyak objek. Dalam banyak kasus, kami bisa saja menandai kelas entitas kami sebagai serializable dan melanjutkannya.

Sekarang, setelah mengatakan semua itu, saya punya teman yang bekerja di Google, pada basis kode yang banyak menggunakan protobuf untuk komunikasi antar modul. Mereka mengambil pendekatan yang sama sekali berbeda: mereka tidak membungkus kelas pesan yang dihasilkan sama sekali, dan dengan antusias mengirimkannya dalam-dalam ke kode mereka. Ini dipandang sebagai hal yang baik, karena ini adalah cara sederhana untuk menjaga antarmuka tetap fleksibel. Tidak ada kode perancah untuk tetap sinkron ketika pesan berkembang, dan kelas yang dihasilkan menyediakan semua hasFoo()metode yang diperlukan untuk menerima kode untuk mendeteksi ada atau tidaknya bidang yang telah ditambahkan dari waktu ke waktu. Ingatlah, bahwa orang yang bekerja di Google cenderung (a) agak pintar dan (b) agak gila.

Tom Anderson
sumber
Pada satu titik, saya melihat menggunakan Serialisasi JBoss sebagai pengganti drop-in untuk serialisasi standar. Itu jauh lebih cepat. Tapi tidak secepat protobuf.
Tom Anderson
Serialisasi JSON menggunakan jackson2 juga cukup cepat. Hal yang saya benci tentang GBP adalah duplikasi yang tidak perlu dari kelas antarmuka utama.
Apoorv Khurasia
0

Untuk menambahkan jawaban Andersons ada garis tipis di pesan bersarang secara cerdik satu sama lain dan berlebihan. Masalahnya adalah setiap pesan menciptakan kelas baru di belakang layar dan segala macam aksesor dan penangan untuk data. Tetapi ada biaya untuk itu jika Anda harus menyalin data atau mengubah satu nilai atau membandingkan pesan. Proses-proses itu bisa sangat lambat dan menyakitkan untuk dilakukan jika Anda memiliki banyak data atau terikat oleh waktu.

Marko Bencik
sumber
2
ini berbunyi lebih seperti komentar tangensial, lihat Bagaimana Menjawab
nyamuk
1
Yah itu tidak: tidak ada domain ada kelasnya semua masalah kata-kata pada akhirnya (oh saya sedang mengembangkan semua hal saya di C ++ tapi ini tidak boleh menjadi masalah)
Marko Bencik