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:
Mewakili pesan dalam file .proto, mengemasnya sebagai toples terpisah, dan mengirimkannya untuk menargetkan konsumen layanan - yang pada dasarnya adalah pendekatan standar yang saya kira.
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.
sumber
Jawaban:
Di mana saya bekerja, keputusan diambil untuk menyembunyikan penggunaan protobuf. Kami tidak mendistribusikan
.proto
file 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:
.proto
filePartyInvitationMessage
kelas, dihasilkan olehprotoc
PartyInvitation
antarmuka, didefinisikan di perpustakaan bersamaActualPartyInvitation
, implementasi konkret yangPartyInvitation
ditentukan oleh aplikasi klien (sebenarnya tidak disebut itu!)StubPartyInvitation
, implementasi sederhana yangPartyInvitation
didefinisikan oleh perpustakaan bersamaPartyInvitationConverter
, yang dapat mengonversi aPartyInvitation
kePartyInvitationMessage
, danPartyInvitationMessage
keStubPartyInvitation
.proto
fileRSVPMessage
kelas, dihasilkan olehprotoc
RSVP
antarmuka, didefinisikan di perpustakaan bersamaActualRSVP
, implementasi konkret yangRSVP
ditentukan oleh aplikasi server (juga tidak benar-benar menyebutnya!)StubRSVP
, implementasi sederhana yangRSVP
didefinisikan oleh perpustakaan bersamaRSVPConverter
, yang dapat mengonversi sebuahRSVP
keRSVPMessage
, danRSVPMessage
keStubRSVP
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.sumber
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.
sumber