- Menentukan Jenis Pesan
- Jenis Nilai Skalar
- Kolom Opsional Dan Nilai Default
- Enumerasi
- Menggunakan Jenis Pesan Lainnya
- Jenis Bertingkat
- Memperbarui Jenis Pesan
- Ekstensi
- Salah satu
- Maps
- Paket
- Menentukan Layanan
- Opsi
- Membuat Kelas
- Lokasi
- Platform yang Didukung
Panduan ini menjelaskan cara menggunakan bahasa buffering protokol untuk membuat struktur data buffering protokol, termasuk sintaksis file .proto
dan cara membuat class akses data dari file .proto
. Ini mencakup versi proto2 bahasa
buffering protokol: untuk informasi tentang sintaksis proto3, lihat
Panduan Bahasa Proto3.
Ini adalah panduan referensi – untuk contoh langkah demi langkah yang menggunakan banyak fitur yang dijelaskan dalam dokumen ini, lihat tutorial untuk bahasa yang Anda pilih.
Menentukan Jenis Pesan
Pertama, mari kita lihat contoh yang sangat sederhana. Misalnya Anda ingin menentukan format pesan permintaan penelusuran, dengan setiap permintaan penelusuran memiliki string kueri, halaman hasil tertentu yang Anda minati, dan sejumlah hasil per halaman. Berikut adalah file .proto
yang Anda gunakan untuk menentukan jenis pesan.
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3;
}
Definisi pesan SearchRequest
menentukan tiga kolom (pasangan nama/nilai), satu kolom untuk setiap bagian data yang ingin Anda sertakan dalam jenis pesan ini. Setiap kolom memiliki nama dan jenis.
Menentukan Jenis Kolom
Pada contoh di atas, semua kolom adalah jenis skalar: dua bilangan bulat (page_number
dan result_per_page
) dan string (query
). Namun, Anda juga dapat menentukan jenis komposit untuk kolom Anda, termasuk enumerasi dan jenis pesan lainnya.
Menetapkan Nomor Kolom
Seperti yang dapat Anda lihat, setiap kolom dalam definisi pesan memiliki angka unik. Angka ini digunakan untuk mengidentifikasi kolom Anda dalam format biner pesan, dan tidak boleh diubah setelah jenis pesan Anda digunakan. Nomor kolom dalam rentang 1 sampai 15 membutuhkan satu byte untuk dienkode, termasuk nomor kolom dan jenis kolom (Anda dapat mengetahui hal ini lebih lanjut di Encoding Buffering Protokol). Nomor kolom dalam rentang 16 hingga 2047 membutuhkan dua byte. Jadi, Anda harus mencadangkan kolom nomor 1 hingga 15 untuk elemen pesan yang sangat sering muncul. Jangan lupa untuk memberikan beberapa ruang untuk elemen yang sering muncul yang mungkin ditambahkan di masa mendatang.
Nomor kolom terkecil yang dapat Anda tentukan adalah 1, dan yang terbesar adalah
229 - 1, atau 536.870.911. Anda juga tidak dapat menggunakan angka 19000
hingga 19999 (FieldDescriptor::kFirstReservedNumber
hingga
FieldDescriptor::kLastReservedNumber
), karena angka tersebut dicadangkan untuk penerapan Buffering
Protokol - compiler buffering protokol akan mengeluh jika Anda menggunakan
salah satu nomor yang dicadangkan ini dalam .proto
. Demikian pula, Anda tidak dapat menggunakan nomor kolom apa pun yang telah dicadangkan sebelumnya.
Menentukan Aturan Kolom
Anda menentukan bahwa kolom pesan adalah salah satu dari berikut ini:
required
: pesan yang diformat dengan baik harus memiliki tepat satu kolom ini.optional
: pesan yang diformat dengan baik dapat memiliki nol atau salah satu dari kolom ini (tetapi tidak boleh lebih dari satu).repeated
: kolom ini dapat diulang berapa kali (termasuk nol) dalam pesan yang diformat dengan baik. Urutan nilai berulang akan dipertahankan.
Untuk alasan historis, kolom repeated
jenis numerik skalar (misalnya,
int32
, int64
, enum
) tidak dienkode secara efisien. Kode baru harus menggunakan opsi khusus [packed = true]
untuk mendapatkan encoding yang lebih efisien. Contoh:
repeated int32 samples = 4 [packed = true];
repeated ProtoEnum results = 5 [packed = true];
Anda dapat mempelajari lebih lanjut encoding packed
di Encoding Buffering Protokol.
Wajib Berlaku Selamanya Anda harus sangat berhati-hati dalam menandai kolom sebagai
required
. Jika pada titik tertentu Anda ingin berhenti menulis atau mengirim kolom wajib diisi, mengubah kolom menjadi kolom opsional akan bermasalah - pembaca lama akan menganggap pesan tanpa kolom ini tidak lengkap dan dapat menolak atau tidak sengaja menghapusnya. Sebaiknya Anda menulis rutinitas validasi kustom khusus aplikasi untuk buffer Anda.
Masalah kedua dengan kolom wajib diisi akan muncul saat seseorang menambahkan nilai ke enum. Dalam hal ini, nilai enum yang tidak dikenal diperlakukan seolah-olah tidak ada, yang juga menyebabkan pemeriksaan nilai yang diperlukan gagal.
Menambahkan Jenis Pesan Lainnya
Beberapa jenis pesan dapat ditentukan dalam satu file .proto
. Hal ini berguna jika Anda menentukan beberapa pesan terkait. Jadi, misalnya, jika ingin menentukan format pesan balasan yang sesuai dengan jenis pesan SearchResponse
, Anda dapat menambahkannya ke .proto
yang sama:
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3;
}
message SearchResponse {
...
}
Menggabungkan Pesan menyebabkan penggelembungan Meskipun beberapa jenis pesan (seperti pesan, enumerasi, dan layanan) dapat ditentukan dalam satu file .proto
, hal ini juga dapat menyebabkan penggelembungan dependensi ketika sejumlah besar pesan dengan dependensi yang bervariasi ditentukan dalam satu file. Sebaiknya sertakan sesedikit mungkin
jenis pesan per file .proto
.
Menambahkan Komentar
Untuk menambahkan komentar ke file .proto
, gunakan //
dan /* ... */
sintaksis gaya C/C++.
/* SearchRequest represents a search query, with pagination options to
* indicate which results to include in the response. */
message SearchRequest {
required string query = 1;
optional int32 page_number = 2; // Which page number do we want?
optional int32 result_per_page = 3; // Number of results to return per page.
}
Kolom yang Direservasi
Jika Anda memperbarui jenis pesan dengan sepenuhnya menghapus kolom, atau mengomentarinya, pengguna di masa mendatang dapat menggunakan kembali nomor kolom tersebut saat melakukan pembaruan pada jenisnya sendiri. Hal ini dapat menyebabkan masalah serius jika nanti mereka memuat versi lama
.proto
yang sama, termasuk kerusakan data, bug privasi, dan sebagainya. Salah satu cara untuk memastikan hal ini tidak terjadi adalah dengan menentukan bahwa nomor kolom (dan/atau nama, yang juga dapat menyebabkan masalah serialisasi JSON) dari kolom yang dihapus adalah reserved
. Compiler buffering protokol akan mengeluh
jika ada pengguna mendatang yang mencoba menggunakan ID kolom ini.
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
Rentang nomor kolom yang dicadangkan inklusif (9 to 11
sama dengan 9, 10,
11
). Perhatikan bahwa Anda tidak dapat menggabungkan nama kolom dan nomor kolom di pernyataan reserved
yang sama.
Apa yang Dihasilkan dari .proto
Anda?
Saat Anda menjalankan compiler buffering protokol pada .proto
, compiler akan menghasilkan kode dalam bahasa pilihan Anda. Anda harus menangani jenis pesan yang telah Anda jelaskan dalam file, termasuk mendapatkan dan menetapkan nilai kolom, membuat serialisasi pesan ke aliran output, dan mengurai pesan dari aliran input.
- Untuk C++, compiler menghasilkan file
.h
dan.cc
dari setiap.proto
, dengan class untuk setiap jenis pesan yang dijelaskan dalam file Anda. - Untuk Java, compiler menghasilkan file
.java
dengan class untuk setiap jenis pesan, serta classBuilder
khusus untuk membuat instance class pesan. - Python sedikit berbeda – compiler Python menghasilkan modul
dengan deskripsi statis untuk setiap jenis pesan di
.proto
, yang kemudian digunakan dengan metaclass untuk membuat class akses data Python yang diperlukan saat runtime. - Untuk Go, compiler akan menghasilkan file
.pb.go
dengan jenis untuk setiap jenis pesan dalam file Anda.
Anda dapat mengetahui lebih lanjut cara menggunakan API untuk setiap bahasa dengan mengikuti tutorial untuk bahasa yang Anda pilih. Untuk mengetahui detail API selengkapnya, lihat referensi API yang relevan.
Jenis Nilai Skalar
Kolom pesan skalar dapat memiliki salah satu jenis berikut – tabel menunjukkan
jenis yang ditentukan dalam file .proto
, dan jenis yang sesuai dalam
class yang dihasilkan secara otomatis:
Jenis .proto | Catatan | Jenis C++ | Jenis Java | Jenis Python[2] | Jenis Go |
---|---|---|---|---|---|
double | double | double | float | *float64 | |
float | float | float | float | *float32 | |
int32 | Menggunakan encoding panjang variabel. Tidak efisien untuk mengenkode angka negatif – jika kolom Anda cenderung memiliki nilai negatif, gunakan sint32. | int32 | int | int | *int32 |
int64 | Menggunakan encoding panjang variabel. Tidak efisien untuk mengenkode angka negatif – jika kolom Anda cenderung memiliki nilai negatif, gunakan sint64. | int64 | long | int/panjang[3] | *int64 |
uint32 | Menggunakan encoding panjang variabel. | uint32 | int[1] | int/panjang[3] | *uint32 |
uint64 | Menggunakan encoding panjang variabel. | uint64 | panjang[1] | int/panjang[3] | *uint64 |
Sint32 | Menggunakan encoding panjang variabel. Nilai int yang ditandatangani. Ini mengenkode angka negatif dengan lebih efisien daripada int32 biasa. | int32 | int | int | *int32 |
Sint64 | Menggunakan encoding panjang variabel. Nilai int yang ditandatangani. Ini mengenkode angka negatif secara lebih efisien daripada int64 biasa. | int64 | long | int/panjang[3] | *int64 |
tetap32 | Selalu empat byte. Lebih efisien daripada uint32 jika nilainya sering lebih besar dari 228. | uint32 | int[1] | int/panjang[3] | *uint32 |
tetap64 | Selalu delapan byte. Lebih efisien daripada uint64 jika nilainya sering lebih besar dari 256. | uint64 | panjang[1] | int/panjang[3] | *uint64 |
Sfix32 | Selalu empat byte. | int32 | int | int | *int32 |
Sfix64 | Selalu delapan byte. | int64 | long | int/panjang[3] | *int64 |
bool | bool | boolean | bool | *bool | |
string | String harus selalu berisi teks berenkode UTF-8. | string | String | unicode (Python 2) atau str (Python 3) | *string |
byte | Dapat berisi urutan byte arbitrer. | string | ByteString | byte | []byte |
Anda dapat mengetahui lebih lanjut cara jenis ini dienkode saat membuat serialisasi pesan di Encoding Buffering Protokol.
[1] Di Java, bilangan bulat 32-bit dan 64-bit yang tidak ditandatangani direpresentasikan menggunakan pasangan tanda tangan, dengan bit teratas hanya disimpan dalam bit tanda.
[2] Dalam semua kasus, menetapkan nilai ke kolom akan melakukan pemeriksaan jenis untuk memastikan validitasnya.
[3] Bilangan bulat 62-bit atau 32-bit yang tidak ditandatangani selalu dinyatakan panjang saat didekode, tetapi dapat menjadi int jika int diberikan saat menetapkan kolom. Dalam semua kasus, nilai harus sesuai dengan jenis yang diwakili saat ditetapkan. Lihat [2].
Kolom Opsional dan Nilai Default
Seperti yang disebutkan di atas, elemen dalam deskripsi pesan dapat diberi label optional
.
Pesan yang diformat dengan baik mungkin berisi atau tidak berisi elemen opsional. Saat pesan diuraikan, jika pesan tidak berisi elemen opsional, mengakses kolom yang sesuai dalam objek yang diuraikan akan menampilkan nilai default untuk kolom tersebut. Nilai default dapat ditentukan sebagai bagian dari deskripsi pesan.
Misalnya, Anda ingin memberikan nilai default 10 untuk
nilai result_per_page
dari SearchRequest
.
optional int32 result_per_page = 3 [default = 10];
Jika nilai default tidak ditentukan untuk elemen opsional, nilai default khusus jenis akan digunakan: untuk string, nilai defaultnya adalah string kosong. Untuk byte, nilai defaultnya adalah string byte kosong. Untuk boom, nilai defaultnya adalah false. Untuk jenis numerik, nilai defaultnya adalah nol. Untuk enum, nilai default adalah nilai pertama yang tercantum dalam definisi jenis enum. Ini berarti Anda harus berhati-hati saat menambahkan nilai ke awal daftar nilai enum. Lihat bagian Memperbarui Jenis Pesan untuk mengetahui panduan tentang cara mengubah definisi dengan aman.
Enumerasi
Saat menentukan jenis pesan, Anda mungkin ingin salah satu kolomnya hanya memiliki salah satu daftar nilai yang telah ditentukan. Misalnya, Anda ingin menambahkan
kolom corpus
untuk setiap SearchRequest
, dengan korpus dapat berupa UNIVERSAL
,
WEB
, IMAGES
, LOCAL
, NEWS
, PRODUCTS
, atau VIDEO
. Anda dapat melakukannya dengan menambahkan enum
ke definisi pesan - kolom dengan jenis enum
hanya dapat memiliki salah satu kumpulan konstanta yang ditentukan sebagai nilainya (jika Anda mencoba memberikan nilai yang berbeda, parser akan memperlakukannya seperti kolom yang tidak dikenal).
Dalam contoh berikut, kami telah menambahkan enum
yang disebut Corpus
dengan semua kemungkinan nilai, dan kolom jenis Corpus
:
enum Corpus {
CORPUS_UNSPECIFIED = 0;
CORPUS_UNIVERSAL = 1;
CORPUS_WEB = 2;
CORPUS_IMAGES = 3;
CORPUS_LOCAL = 4;
CORPUS_NEWS = 5;
CORPUS_PRODUCTS = 6;
CORPUS_VIDEO = 7;
}
message SearchRequest {
required string query = 1;
optional int32 page_number = 2;
optional int32 result_per_page = 3 [default = 10];
optional Corpus corpus = 4 [default = CORPUS_UNIVERSAL];
}
Anda dapat menentukan alias dengan menetapkan nilai yang sama ke konstanta enum yang berbeda.
Untuk melakukannya, Anda harus menetapkan opsi allow_alias
ke true
. Jika tidak, compiler buffering protokol akan menghasilkan pesan error saat alias ditemukan.
Meskipun semua nilai alias valid selama deserialisasi, nilai pertama
selalu digunakan saat serialisasi.
enum EnumAllowingAlias {
option allow_alias = true;
EAA_UNSPECIFIED = 0;
EAA_STARTED = 1;
EAA_RUNNING = 1;
EAA_FINISHED = 2;
}
enum EnumNotAllowingAlias {
ENAA_UNSPECIFIED = 0;
ENAA_STARTED = 1;
// ENAA_RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside.
ENAA_FINISHED = 2;
}
Konstanta enumerasi harus dalam rentang bilangan bulat 32-bit. Karena nilai enum
menggunakan encoding bervariasi pada kabel, nilai negatif tidak efisien dan sehingga tidak direkomendasikan. Anda dapat menentukan enum
dalam definisi pesan atau di luar – enum
ini dapat digunakan kembali dalam definisi pesan apa pun dalam file .proto
. Anda juga dapat menggunakan jenis enum
yang dideklarasikan dalam satu pesan sebagai jenis kolom dalam pesan lain, menggunakan sintaksis _MessageType_._EnumType_
.
Saat Anda menjalankan compiler buffering protokol di .proto
yang menggunakan enum
, kode yang dihasilkan akan memiliki enum
yang sesuai untuk Java atau C++, atau class EnumDescriptor
khusus untuk Python yang digunakan untuk membuat kumpulan konstanta simbolis dengan
nilai bilangan bulat di class yang dihasilkan runtime.
Untuk informasi selengkapnya tentang cara menangani pesan enum
dalam aplikasi Anda, lihat panduan kode yang dihasilkan untuk bahasa yang Anda pilih.
Nilai yang Dicadangkan
Jika Anda memperbarui jenis enum dengan menghapus entri enum sepenuhnya atau
memberi komentar, pengguna mendatang dapat menggunakan kembali nilai numerik tersebut saat melakukan
pembaruan untuk jenis tersebut. Hal ini dapat menyebabkan masalah serius jika nanti mereka memuat versi lama
.proto
yang sama, termasuk kerusakan data, bug privasi, dan sebagainya. Salah satu cara untuk memastikan hal ini tidak terjadi adalah dengan menentukan bahwa nilai
numerik (dan/atau nama, yang juga dapat menyebabkan masalah untuk serialisasi JSON) dari
entri yang dihapus adalah reserved
. Compiler buffering protokol akan mengeluh
jika ada pengguna mendatang yang mencoba menggunakan ID ini. Anda dapat menentukan bahwa rentang nilai numerik yang dicadangkan hingga nilai maksimum yang memungkinkan menggunakan kata kunci max
.
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
Perlu diperhatikan bahwa Anda tidak dapat menggabungkan nama kolom dan nilai numerik dalam pernyataan reserved
yang sama.
Menggunakan Jenis Pesan Lainnya
Anda dapat menggunakan jenis pesan lainnya sebagai jenis kolom. Misalnya, Anda ingin menyertakan pesan Result
di setiap pesan SearchResponse
. Untuk melakukannya, Anda dapat menentukan jenis pesan Result
di .proto
yang sama, lalu menentukan kolom jenis Result
di SearchResponse
:
message SearchResponse {
repeated Result result = 1;
}
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}
Mengimpor Definisi
Pada contoh di atas, jenis pesan Result
ditentukan dalam file yang sama dengan
SearchResponse
– bagaimana jika jenis pesan yang ingin Anda gunakan sebagai jenis kolom sudah
ditentukan di file .proto
lain?
Anda dapat menggunakan definisi dari file .proto
lain dengan mengimpornya. Untuk mengimpor definisi .proto
lain, tambahkan pernyataan impor ke bagian atas file:
import "myproject/other_protos.proto";
Secara default, Anda hanya dapat menggunakan definisi dari file .proto
yang diimpor secara langsung.
Namun, terkadang Anda mungkin perlu memindahkan file .proto
ke lokasi baru.
Daripada memindahkan file .proto
secara langsung dan memperbarui semua situs panggilan dalam
satu perubahan, Anda dapat menempatkan file .proto
placeholder di lokasi lama untuk
meneruskan semua impor ke lokasi baru menggunakan gagasan import public
.
Perhatikan bahwa fungsi impor publik tidak tersedia di Java.
Dependensi import public
dapat digunakan secara transitif oleh kode apa pun yang mengimpor proto yang berisi pernyataan import public
. Contoh:
// new.proto
// All definitions are moved here
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto
Compiler protokol menelusuri file yang diimpor dalam kumpulan direktori
yang ditentukan pada command line compiler protokol menggunakan flag -I
/--proto_path
. Jika tidak ada tanda yang diberikan, flag akan terlihat di direktori tempat compiler dipanggil. Secara umum, Anda harus menetapkan flag --proto_path
ke root project dan menggunakan nama yang sepenuhnya memenuhi syarat untuk semua impor.
Menggunakan Jenis Pesan proto3
Anda dapat mengimpor jenis pesan proto3 dan menggunakannya dalam pesan proto2, dan sebaliknya. Namun, enum proto2 tidak dapat digunakan dalam sintaksis proto3.
Jenis Bertingkat
Anda dapat menentukan dan menggunakan jenis pesan di dalam jenis pesan lainnya, seperti dalam contoh berikut. Di sini, pesan Result
ditentukan di dalam pesan SearchResponse
:
message SearchResponse {
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}
repeated Result result = 1;
}
Jika ingin menggunakan kembali jenis pesan ini di luar jenis pesan induknya, Anda merujuknya sebagai _Parent_._Type_
:
message SomeOtherMessage {
optional SearchResponse.Result result = 1;
}
Anda dapat membuat rangkaian pesan sedalam yang Anda inginkan. Pada contoh di bawah, perhatikan bahwa dua jenis bertingkat bernama Inner
sepenuhnya independen, karena keduanya ditentukan dalam pesan yang berbeda:
message Outer { // Level 0
message MiddleAA { // Level 1
message Inner { // Level 2
optional int64 ival = 1;
optional bool booly = 2;
}
}
message MiddleBB { // Level 1
message Inner { // Level 2
optional string name = 1;
optional bool flag = 2;
}
}
}
Grup
Perhatikan bahwa fitur grup tidak digunakan lagi dan tidak boleh digunakan saat membuat jenis pesan baru. Gunakan jenis pesan bertingkat sebagai gantinya.
Grup adalah cara lain untuk membuat tingkatan informasi dalam definisi pesan. Misalnya, cara lain untuk menentukan SearchResponse
yang berisi sejumlah
Result
adalah sebagai berikut:
message SearchResponse {
repeated group Result = 1 {
required string url = 2;
optional string title = 3;
repeated string snippets = 4;
}
}
Grup hanya menggabungkan jenis pesan bertingkat dan kolom menjadi satu deklarasi. Dalam kode, Anda dapat memperlakukan pesan ini seolah-olah memiliki
kolom jenis Result
bernama result
(nama yang terakhir dikonversi menjadi huruf kecil
sehingga tidak bertentangan dengan yang pertama). Oleh karena itu, contoh ini sama persis dengan SearchResponse
di atas, kecuali bahwa pesan tersebut memiliki format kabel yang berbeda.
Memperbarui Jenis Pesan
Jika jenis pesan yang ada tidak lagi memenuhi semua kebutuhan Anda, misalnya, Anda ingin format pesan memiliki kolom tambahan, tetapi Anda masih ingin menggunakan kode yang dibuat dengan format lama, jangan khawatir. Sangat mudah untuk memperbarui jenis pesan tanpa merusak kode yang ada saat Anda menggunakan format bank biner.
Jika Anda menggunakan format kabel biner, periksa aturan berikut:
- Jangan ubah nomor kolom untuk kolom yang ada.
- Setiap kolom baru yang Anda tambahkan harus berupa
optional
ataurepeated
. Artinya, setiap pesan yang diserialisasi berdasarkan kode menggunakan format pesan "lama" dapat diurai oleh kode baru yang dihasilkan, karena elemen tersebut tidak akan kehilangan elemenrequired
. Anda harus menyiapkan nilai default yang logis untuk elemen-elemen ini agar kode baru dapat berinteraksi secara benar dengan pesan yang dihasilkan oleh kode lama. Demikian pula, pesan yang dibuat oleh kode baru dapat diurai oleh kode lama Anda: biner lama mengabaikan kolom baru saat mengurai. Namun, kolom yang tidak dikenal tidak akan dihapus, dan jika diserialisasi kemudian diserialisasi, kolom yang tidak dikenal akan diserialisasi bersama – jadi jika pesan diteruskan ke kode baru, kolom baru akan tetap tersedia. - Kolom yang tidak wajib dapat dihapus, asalkan nomor kolom tidak digunakan lagi di jenis pesan yang diperbarui. Sebagai gantinya, Anda dapat mengganti nama kolom, misalnya menambahkan awalan "OBSOLETE_", atau membuat nomor kolom dicadangkan, sehingga pengguna
.proto
di masa mendatang tidak dapat menggunakan kembali nomor tersebut secara tidak sengaja. - Kolom yang tidak diperlukan dapat dikonversi menjadi ekstensi dan sebaliknya, asalkan jenis dan nomornya tetap sama.
int32
,uint32
,int64
,uint64
, danbool
semuanya kompatibel. Ini berarti Anda dapat mengubah kolom dari salah satu jenis ini ke jenis lainnya tanpa merusak kompatibilitas dengan versi baru atau mundur. Jika angka diurai dari kabel yang tidak sesuai dengan jenis yang sesuai, Anda akan mendapatkan efek yang sama seolah-olah Anda telah mentransmisikan nomor ke jenis tersebut di C++ (misalnya, jika angka 64-bit dibaca sebagai int32, angka tersebut akan terpotong menjadi 32 bit).sint32
dansint64
kompatibel satu sama lain, tetapi tidak kompatibel dengan jenis bilangan bulat lainnya.string
danbytes
kompatibel selama byte-nya adalah UTF-8 yang valid.- Pesan tersemat kompatibel dengan
bytes
jika byte berisi versi pesan yang dienkode. fixed32
kompatibel dengansfixed32
, danfixed64
dengansfixed64
.- Untuk kolom
string
,bytes
, dan pesan,optional
kompatibel denganrepeated
. Dengan mempertimbangkan data serial kolom berulang sebagai input, klien yang mengharapkan kolom ini menjadioptional
akan mengambil nilai input terakhir jika itu adalah kolom jenis primitif atau menggabungkan semua elemen input jika itu adalah kolom jenis pesan. Perhatikan bahwa ini biasanya tidak aman untuk jenis numerik, termasuk boom dan enum. Kolom berulang jenis numerik dapat diserialisasi dalam format dikemas, yang tidak akan diurai dengan benar saat kolomoptional
diharapkan. - Mengubah nilai default umumnya tidak menjadi masalah, selama Anda ingat bahwa nilai default tidak pernah dikirim melalui kabel. Oleh karena itu, jika suatu program menerima pesan yang kolom tertentunya tidak ditetapkan, program akan melihat nilai default seperti yang ditentukan dalam versi protokol program tersebut. Pengirim tidak akan melihat nilai default yang ditentukan dalam kode pengirim.
enum
kompatibel denganint32
,uint32
,int64
, danuint64
dalam hal format kabel (perhatikan bahwa nilai akan terpotong jika tidak sesuai), tetapi perhatikan bahwa kode klien dapat memperlakukannya secara berbeda saat pesan dideserialisasi. Secara khusus, nilaienum
yang tidak dikenal akan dihapus saat pesan dideserialisasi, yang membuat pengakseshas..
kolom menampilkan nilai salah dan pengambilnya akan menampilkan nilai pertama yang tercantum dalam definisienum
, atau nilai default jika nilai tersebut ditentukan. Dalam kasus kolom enum berulang, nilai apa pun yang tidak dikenal akan dihapus dari daftar. Namun, kolom bilangan bulat akan selalu mempertahankan nilainya. Oleh karena itu, Anda harus sangat berhati-hatilah saat mengupgrade bilangan bulat keenum
dalam hal menerima nilai enum di luar batas pada kabel.- Dalam implementasi Java dan C++ saat ini, jika nilai
enum
yang tidak dikenal dihapus, nilai tersebut akan disimpan bersama kolom lain yang tidak diketahui. Perhatikan bahwa hal ini dapat menyebabkan perilaku aneh jika data ini diserialisasi, lalu di-reparse oleh klien yang mengenali nilai tersebut. Untuk kolom opsional, meskipun nilai baru ditulis setelah pesan asli di-deserialisasi, nilai lama akan tetap dibaca oleh klien yang mengenalinya. Dalam kasus kolom berulang, nilai lama akan muncul setelah nilai yang diakui dan baru ditambahkan, yang berarti pesanan tidak akan dipertahankan. - Mengubah satu kolom atau ekstensi
optional
menjadi anggotaoneof
baru akan kompatibel secara biner, tetapi untuk beberapa bahasa (terutama, Go), API kode yang dihasilkan akan berubah dengan cara yang tidak kompatibel. Karena alasan ini, Google tidak melakukan perubahan tersebut pada API publiknya, seperti yang didokumentasikan dalam AIP-180. Dengan peringatan yang sama tentang kompatibilitas sumber, memindahkan beberapa kolom keoneof
baru mungkin aman jika Anda yakin bahwa tidak ada kode yang menyetel lebih dari satu per satu. Memindahkan kolom keoneof
yang ada tidak aman. Demikian juga, mengubah satu kolomoneof
menjadi kolom atau ekstensioptional
aman. - Mengubah kolom antara
map<K, V>
dan kolom pesanrepeated
yang sesuai memiliki format biner yang kompatibel (lihat Maps, di bawah, untuk tata letak pesan dan batasan lainnya). Namun, keamanan perubahan bergantung pada aplikasi: saat melakukan serialisasi dan menserialisasi ulang pesan, klien yang menggunakan definisi kolomrepeated
akan menghasilkan hasil identik secara semantik; namun, klien yang menggunakan definisi kolommap
dapat mengurutkan ulang entri dan menghapus entri dengan kunci duplikat.
Ekstensi
Ekstensi memungkinkan Anda mendeklarasikan bahwa rentang nomor kolom dalam pesan tersedia untuk ekstensi pihak ketiga. Ekstensi adalah placeholder untuk kolom yang jenisnya tidak ditentukan oleh file .proto
asli. Hal ini memungkinkan file .proto
lainnya ditambahkan ke definisi pesan Anda dengan menentukan jenis beberapa atau semua kolom dengan nomor kolom tersebut. Lihat contoh berikut:
message Foo {
// ...
extensions 100 to 199;
}
Ini mengatakan bahwa rentang nomor kolom [100, 199] di Foo
dicadangkan untuk ekstensi. Pengguna lain kini dapat menambahkan kolom baru ke Foo
dalam file .proto
mereka sendiri
yang mengimpor .proto
, menggunakan nomor kolom dalam rentang yang ditentukan
– misalnya:
extend Foo {
optional int32 bar = 126;
}
Tindakan ini akan menambahkan kolom bernama bar
dengan nomor kolom 126 ke definisi asli Foo
.
Saat pesan Foo
pengguna dienkode, format kabel sama persis seperti jika pengguna menentukan kolom baru di dalam Foo
. Namun, cara Anda mengakses kolom ekstensi dalam kode aplikasi sedikit berbeda dengan mengakses kolom reguler. Kode akses data yang dihasilkan memiliki pengakses khusus untuk menggunakan ekstensi. Jadi, misalnya, berikut adalah cara menetapkan nilai bar
di C++:
Foo foo;
foo.SetExtension(bar, 15);
Demikian pula, class Foo
menentukan pengakses template HasExtension()
,
ClearExtension()
, GetExtension()
, MutableExtension()
, dan
AddExtension()
. Semua memiliki semantik yang cocok dengan pengakses pengakses yang dihasilkan untuk kolom normal. Untuk informasi selengkapnya tentang menggunakan ekstensi, lihat referensi kode yang dihasilkan untuk bahasa yang Anda pilih.
Perhatikan bahwa ekstensi dapat berupa jenis kolom apa pun, termasuk jenis pesan, tetapi tidak boleh salah satu dari elemen peta atau peta.
Ekstensi Bertingkat
Anda dapat mendeklarasikan ekstensi dalam cakupan jenis lain:
message Baz {
extend Foo {
optional int32 bar = 126;
}
...
}
Dalam hal ini, kode C++ untuk mengakses ekstensi ini adalah:
Foo foo;
foo.SetExtension(Baz::bar, 15);
Dengan kata lain, satu-satunya efek adalah bar
ditentukan dalam cakupan
Baz
.
Hal ini adalah sumber umum kebingungan: Mendeklarasikan blok extend
yang bertingkat di dalam
jenis pesan tidak menyiratkan hubungan apa pun antara jenis luar dan
jenis ekstensi. Secara khusus, contoh di atas tidak berarti bahwa Baz
adalah
jenis subclass Foo
. Artinya, simbol bar
dideklarasikan
dalam cakupan Baz
; fungsi ini hanya merupakan anggota statis.
Pola yang umum adalah menentukan ekstensi di dalam cakupan jenis kolom ekstensi – misalnya, berikut ekstensi ke Foo
dari jenis Baz
, dengan ekstensi yang ditetapkan sebagai bagian dari Baz
:
message Baz {
extend Foo {
optional Baz foo_ext = 127;
}
...
}
Namun, tidak ada persyaratan bahwa ekstensi dengan jenis pesan ditentukan di dalam jenis tersebut. Anda juga dapat melakukan hal berikut:
message Baz {
...
}
// This can even be in a different file.
extend Foo {
optional Baz foo_baz_ext = 127;
}
Faktanya, sintaksis ini mungkin lebih disukai untuk menghindari kebingungan. Seperti yang disebutkan di atas, sintaksis bertingkat sering kali salah diartikan sebagai subclass oleh pengguna yang belum familier dengan ekstensi.
Memilih Nomor Ekstensi
Sangat penting untuk memastikan bahwa dua pengguna tidak menambahkan ekstensi ke jenis pesan yang sama menggunakan nomor kolom yang sama, kerusakan data dapat terjadi jika ekstensi keliru ditafsirkan sebagai jenis yang salah. Anda mungkin ingin mempertimbangkan untuk menentukan konvensi penomoran ekstensi untuk project Anda agar hal ini tidak terjadi.
Jika konvensi penomoran Anda mungkin melibatkan ekstensi yang memiliki angka kolom yang sangat besar, Anda dapat menentukan bahwa rentang ekstensi Anda mencapai jumlah kolom maksimum yang dimungkinkan menggunakan kata kunci max
:
message Foo {
extensions 1000 to max;
}
max
adalah 229 - 1, atau 536.870.911.
Seperti saat memilih nomor kolom secara umum, konvensi penomoran Anda juga harus menghindari nomor kolom 19000 sampai 19999 (FieldDescriptor::kFirstReservedNumber
hingga FieldDescriptor::kLastReservedNumber
), karena angka tersebut dicadangkan untuk penerapan Buffering Protokol. Anda dapat menentukan rentang ekstensi yang menyertakan rentang ini, tetapi compiler protokol tidak akan mengizinkan Anda menentukan ekstensi sebenarnya dengan nomor tersebut.
Salah satu
Jika Anda memiliki pesan dengan banyak kolom opsional dan di mana paling banyak satu kolom akan ditetapkan secara bersamaan, Anda dapat menerapkan perilaku ini dan menghemat memori dengan menggunakan salah satu fitur.
Salah satu kolom bersifat seperti kolom opsional, kecuali semua kolom dalam salah satu memori bersama, dan maksimal satu kolom dapat ditetapkan sekaligus. Menetapkan salah satu anggota akan otomatis menghapus semua anggota lainnya. Anda dapat memeriksa nilai mana
di salah satu yang ditetapkan (jika ada) menggunakan metode case()
atau WhichOneof()
khusus,
bergantung pada bahasa yang Anda pilih.
Menggunakan Oneof
Untuk menentukan salah satu nilai dalam .proto
, gunakan kata kunci oneof
diikuti dengan
nama salah satu, dalam hal ini test_oneof
:
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
Anda kemudian dapat menambahkan salah satu kolom tersebut ke definisi salah satunya. Anda dapat menambahkan kolom jenis apa pun, tetapi tidak dapat menggunakan kata kunci required
, optional
, atau repeated
. Jika perlu menambahkan kolom berulang ke salah satunya, Anda dapat menggunakan pesan yang berisi kolom berulang.
Dalam kode yang Anda buat, salah satu kolom memiliki pengambil dan penyetel yang sama seperti metode optional
biasa. Anda juga akan mendapatkan metode khusus untuk memeriksa nilai mana (jika ada) dalam salah satunya yang telah ditetapkan. Anda dapat mengetahui lebih lanjut tentang salah satu API ini untuk bahasa yang Anda pilih dalam referensi API yang relevan.
Salah Satu Fitur
Menetapkan kolom salah satu akan otomatis menghapus semua anggota lainnya. Jadi, jika Anda menetapkan beberapa kolom, hanya kolom terakhir yang Anda tetapkan yang akan tetap memiliki nilai.
SampleMessage message; message.set_name("name"); CHECK(message.has_name()); message.mutable_sub_message(); // Will clear name field. CHECK(!message.has_name());
Jika parser menemukan beberapa anggota dari yang sama pada kabel, hanya anggota terakhir yang terlihat yang digunakan dalam pesan yang diurai.
Ekstensi tidak didukung untuk ini.
Salah satunya tidak boleh
repeated
.API Refleksi berfungsi untuk salah satu kolom.
Jika Anda menetapkan salah satu kolom ke nilai default (misalnya menetapkan satu kolom int32 ke 0), "kasus" salah satu kolom tersebut akan ditetapkan, dan nilainya akan diserialisasi pada kabel.
Jika Anda menggunakan C++, pastikan kode Anda tidak menyebabkan error memori. Kode contoh berikut akan error karena
sub_message
sudah dihapus dengan memanggil metodeset_name()
.SampleMessage message; SubMessage* sub_message = message.mutable_sub_message(); message.set_name("name"); // Will delete sub_message sub_message->set_... // Crashes here
Sekali lagi di C++, jika Anda melakukan
Swap()
pada dua pesan dengan salah satunya, setiap pesan akan berakhir dengan salah satunya: dalam contoh di bawah,msg1
akan memilikisub_message
danmsg2
akan memilikiname
.SampleMessage msg1; msg1.set_name("name"); SampleMessage msg2; msg2.mutable_sub_message(); msg1.swap(&msg2); CHECK(msg1.has_sub_message()); CHECK(msg2.has_name());
Masalah kompatibilitas mundur
Hati-hati saat menambahkan atau menghapus salah satu kolom. Jika memeriksa nilai
salah satu nilai akan menampilkan None
/NOT_SET
, hal ini dapat berarti bahwa objek tersebut belum ditetapkan atau
telah ditetapkan ke kolom dalam versi yang berbeda darinya. Tidak ada cara untuk membedakannya, karena tidak ada cara untuk mengetahui apakah kolom yang tidak diketahui pada kabel merupakan anggota dari salah satunya.
Masalah Penggunaan Ulang Tag
- Memindahkan kolom opsional ke dalam atau ke salah satu kolom: Anda dapat kehilangan beberapa informasi (beberapa kolom akan dihapus) setelah pesan diserialisasi dan diuraikan. Namun, Anda dapat memindahkan satu kolom dengan aman ke kolom baru dan mungkin dapat memindahkan beberapa kolom jika diketahui bahwa hanya satu kolom yang pernah ditetapkan. Lihat Memperbarui Jenis Pesan untuk detail selengkapnya.
- Menghapus salah satu kolom dan menambahkannya kembali: Tindakan ini dapat menghapus kolom salah satu yang telah Anda tetapkan saat ini setelah diserialisasi dan diuraikan.
- Memisahkan atau menggabungkan salah satunya: Masalah ini memiliki masalah serupa dengan memindahkan kolom
optional
reguler.
Peta
Jika Anda ingin membuat peta asosiatif sebagai bagian dari definisi data, buffering protokol menyediakan sintaksis pintasan yang praktis:
map<key_type, value_type> map_field = N;
...dengan key_type
dapat berupa jenis string atau integral (sehingga, semua jenis
skalar kecuali untuk jenis floating point dan bytes
). Perhatikan bahwa
enum bukan key_type
yang valid. value_type
dapat berupa jenis apa pun kecuali peta lain.
Jadi, misalnya, jika ingin membuat peta project tempat setiap pesan Project
dikaitkan dengan kunci string, Anda dapat mendefinisikannya seperti ini:
map<string, Project> projects = 3;
API peta yang dihasilkan saat ini tersedia untuk semua bahasa yang didukung proto2. Anda dapat mengetahui lebih lanjut tentang API peta untuk bahasa yang Anda pilih dalam referensi API yang relevan.
Fitur Maps
- Ekstensi tidak didukung untuk peta.
- Maps tidak boleh berupa
repeated
,optional
, ataurequired
. - Urutan format Wire dan pengurutan iterasi iterasi nilai peta tidak ditentukan, sehingga Anda tidak dapat mengandalkan item peta dalam urutan tertentu.
- Saat membuat format teks untuk
.proto
, peta diurutkan berdasarkan kunci. Kunci numerik diurutkan secara numerik. - Saat menguraikan dari kabel atau saat menggabungkan, jika ada kunci peta duplikat, kunci terakhir yang terlihat akan digunakan. Saat mengurai peta dari format teks, penguraian dapat gagal jika ada kunci duplikat.
Kompatibilitas mundur
Sintaksis peta setara dengan yang berikut pada kabel, sehingga penerapan buffering protokol yang tidak mendukung peta masih dapat menangani data Anda:
message MapFieldEntry {
optional key_type key = 1;
optional value_type value = 2;
}
repeated MapFieldEntry map_field = N;
Setiap implementasi buffering protokol yang mendukung peta harus menghasilkan dan menerima data yang dapat diterima oleh definisi di atas.
Paket
Anda dapat menambahkan penentu package
opsional ke file .proto
untuk mencegah konflik nama antara jenis pesan protokol.
package foo.bar;
message Open { ... }
Kemudian, Anda dapat menggunakan penentu paket saat menentukan kolom jenis pesan:
message Foo {
...
required foo.bar.Open open = 1;
...
}
Cara penentu paket memengaruhi kode yang dihasilkan bergantung pada bahasa yang Anda pilih:
- Di C++, class yang dihasilkan digabungkan dalam namespace C++. Misalnya,
Open
akan berada di namespacefoo::bar
. - Di Java, paket digunakan sebagai paket Java, kecuali jika Anda secara eksplisit
menyediakan
option java_package
dalam file.proto
. - Di Python, perintah
package
diabaikan, karena modul Python diatur sesuai dengan lokasinya di sistem file. - Di Go, perintah
package
diabaikan, dan file.pb.go
yang dihasilkan berada dalam paket yang dinamai menurut aturango_proto_library
yang sesuai.
Perhatikan bahwa meskipun perintah package
tidak secara langsung memengaruhi
kode yang dihasilkan, misalnya dalam Python, sangat disarankan untuk
menentukan paket untuk file .proto
, karena dapat menyebabkan konflik
penamaan dalam deskriptor dan membuat proto tidak dapat portabel untuk bahasa lain.
Resolusi Nama dan Paket
Resolusi nama jenis dalam bahasa buffering protokol berfungsi seperti C++: pertama-tama
cakupan terdalam ditelusuri, lalu berikutnya terdalam, dan seterusnya, dengan setiap
paket dianggap "dalam" untuk paket induknya. '.' di depan (misalnya, .foo.bar.Baz
) berarti memulai dari cakupan terluar.
Compiler buffering protokol menyelesaikan semua nama jenis dengan mengurai file
.proto
yang diimpor. Generator kode untuk setiap bahasa mengetahui cara merujuk ke setiap jenis dalam bahasa tersebut, meskipun memiliki aturan pencakupan yang berbeda.
Menentukan Layanan
Jika ingin menggunakan jenis pesan dengan sistem RPC (Remote Procedure Call)
, Anda dapat menentukan antarmuka layanan RPC dalam file .proto
dan
compiler buffering protokol akan menghasilkan kode antarmuka stub dan stub dalam
bahasa yang Anda pilih. Jadi, misalnya, jika ingin menentukan layanan RPC dengan
metode yang mengambil SearchRequest
dan menampilkan SearchResponse
, Anda dapat
menentukannya di file .proto
sebagai berikut:
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse);
}
Secara default, compiler protokol kemudian akan menghasilkan antarmuka abstrak
yang disebut SearchService
dan implementasi "stub" yang sesuai. Potongan
akan meneruskan semua panggilan ke RpcChannel
, yang merupakan antarmuka abstrak
yang harus Anda tentukan sendiri dalam hal sistem RPC Anda sendiri. Misalnya, Anda mungkin mengimplementasikan RpcChannel
yang membuat serialisasi pesan dan mengirimkannya ke server melalui HTTP. Dengan kata lain, stub yang dihasilkan menyediakan antarmuka aman jenis untuk membuat panggilan RPC berbasis protokol, tanpa mengunci Anda pada implementasi RPC tertentu. Jadi, dalam C++, Anda mungkin akan mendapatkan kode seperti ini:
using google::protobuf;
protobuf::RpcChannel* channel;
protobuf::RpcController* controller;
SearchService* service;
SearchRequest request;
SearchResponse response;
void DoSearch() {
// You provide classes MyRpcChannel and MyRpcController, which implement
// the abstract interfaces protobuf::RpcChannel and protobuf::RpcController.
channel = new MyRpcChannel("somehost.example.com:1234");
controller = new MyRpcController;
// The protocol compiler generates the SearchService class based on the
// definition given above.
service = new SearchService::Stub(channel);
// Set up the request.
request.set_query("protocol buffers");
// Execute the RPC.
service->Search(controller, &request, &response,
protobuf::NewCallback(&Done));
}
void Done() {
delete service;
delete channel;
delete controller;
}
Semua class layanan juga mengimplementasikan antarmuka Service
, yang menyediakan cara
untuk memanggil metode tertentu tanpa mengetahui nama metode atau jenis input
dan output-nya pada waktu kompilasi. Di sisi server, kode ini dapat digunakan untuk menerapkan server RPC yang dapat Anda gunakan untuk mendaftarkan layanan.
using google::protobuf;
class ExampleSearchService : public SearchService {
public:
void Search(protobuf::RpcController* controller,
const SearchRequest* request,
SearchResponse* response,
protobuf::Closure* done) {
if (request->query() == "google") {
response->add_result()->set_url("http://www.google.com");
} else if (request->query() == "protocol buffers") {
response->add_result()->set_url("http://protobuf.googlecode.com");
}
done->Run();
}
};
int main() {
// You provide class MyRpcServer. It does not have to implement any
// particular interface; this is just an example.
MyRpcServer server;
protobuf::Service* service = new ExampleSearchService;
server.ExportOnPort(1234, service);
server.Run();
delete service;
return 0;
}
Jika tidak ingin memasukkan sistem RPC yang ada, Anda kini dapat menggunakan
gRPC: sistem RPC open source
bahasa dan platform yang dikembangkan di Google. gRPC berfungsi sangat baik dengan
buffering protokol dan memungkinkan Anda membuat kode RPC yang relevan langsung dari file
.proto
menggunakan plugin compiler buffering protokol khusus. Namun, karena
ada potensi masalah kompatibilitas antara klien dan server yang dihasilkan
dengan proto2 dan proto3, sebaiknya gunakan proto3 untuk menentukan layanan
gRPC. Anda dapat mengetahui lebih lanjut tentang sintaksis proto3 di
Panduan Bahasa Proto3. Jika ingin menggunakan proto2 dengan gRPC, Anda harus menggunakan library dan compiler buffering protokol versi 3.0.0 atau yang lebih tinggi.
Selain gRPC, ada juga sejumlah project pihak ketiga yang sedang berlangsung untuk mengembangkan implementasi RPC untuk Protocol Buffer. Untuk melihat daftar link ke project yang kami ketahui, lihat halaman wiki add-on pihak ketiga.
Opsi
Masing-masing deklarasi dalam file .proto
dapat dianotasi dengan sejumlah
opsi. Opsi tidak mengubah arti deklarasi secara keseluruhan, tetapi dapat
memengaruhi cara penanganannya dalam konteks tertentu. Daftar lengkap
opsi yang tersedia ditentukan dalam
/google/protobuf/descriptor.proto
.
Beberapa opsinya adalah opsi tingkat file, yang berarti opsi harus ditulis pada cakupan tingkat atas, bukan di dalam pesan, enum, atau definisi layanan. Beberapa opsinya adalah opsi tingkat pesan, yang berarti opsi tersebut harus ditulis dalam definisi pesan. Beberapa opsinya adalah opsi tingkat kolom, yang berarti opsi tersebut harus ditulis dalam definisi kolom. Opsi juga dapat ditulis pada jenis enum, nilai enum, salah satu kolom, jenis layanan, dan metode layanan. Namun, saat ini tidak ada opsi yang berguna untuk semua opsi ini.
Berikut adalah beberapa opsi yang paling umum digunakan:
java_package
(opsi file): Paket yang ingin Anda gunakan untuk class Java yang dihasilkan. Jika tidak ada opsijava_package
eksplisit yang diberikan dalam file.proto
, secara default paket proto (yang ditentukan menggunakan kata kunci "paket" dalam file.proto
) akan digunakan. Namun, paket proto biasanya tidak membuat paket Java yang baik karena paket proto diharapkan tidak dimulai dengan nama domain terbalik. Jika tidak menghasilkan kode Java, opsi ini tidak berpengaruh.option java_package = "com.example.foo";
java_outer_classname
(opsi file): Nama class (dan karenanya nama file) untuk class Java wrapper yang ingin Anda buat. Jika tidak adajava_outer_classname
eksplisit yang ditentukan dalam file.proto
, nama class akan dibuat dengan mengonversi nama file.proto
menjadi camel-case (sehinggafoo_bar.proto
menjadiFooBar.java
). Jika opsijava_multiple_files
dinonaktifkan, maka semua class/enums/etc. lainnya yang dihasilkan untuk file.proto
akan dihasilkan dalam class Java wrapper luar ini sebagai class/enums bertingkat/dll. Jika tidak menghasilkan kode Java, opsi ini tidak akan berpengaruh.option java_outer_classname = "Ponycopter";
java_multiple_files
(opsi file): Jika salah, hanya satu file.java
yang akan dibuat untuk file.proto
ini, dan semua class /enumerasi Java/dll. yang dihasilkan untuk pesan, layanan, dan enumerasi level teratas akan ditempatkan di dalam class luar (lihatjava_outer_classname
). Jika true, file.java
terpisah, class/pesan enum, default, dan default akan tersedia untuk setiap class/enum pesan Java, class enum, dan enum, serta jenis paket penyimpanan data dan kode enum, penyimpanan default, penyimpanan, dan repositori. Jika tidak menghasilkan kode Java, opsi ini tidak akan berpengaruh.option java_multiple_files = true;
optimize_for
(opsi file): Dapat disetel keSPEED
,CODE_SIZE
, atauLITE_RUNTIME
. Hal ini memengaruhi generator kode C++ dan Java (dan mungkin generator pihak ketiga) dengan cara berikut:SPEED
(default): Compiler buffering protokol akan menghasilkan kode untuk serialisasi, penguraian, dan melakukan operasi umum lainnya pada jenis pesan Anda. Kode ini sangat dioptimalkan.CODE_SIZE
: Compiler buffering protokol akan menghasilkan class minimal dan akan mengandalkan kode bersama berbasis refleksi untuk mengimplementasikan serialisasi, penguraian, dan berbagai operasi lainnya. Dengan demikian, kode yang dihasilkan akan jauh lebih kecil daripada denganSPEED
, tetapi operasinya akan lebih lambat. Class masih akan menerapkan API publik yang sama persis seperti yang ada di modeSPEED
. Mode ini paling berguna dalam aplikasi yang berisi file.proto
dalam jumlah yang sangat besar dan tidak memerlukan semuanya agar berfungsi dengan sangat cepat.LITE_RUNTIME
: Compiler buffering protokol akan menghasilkan class yang hanya bergantung pada library runtime "lite" (libprotobuf-lite
bukanlibprotobuf
). Runtime lite jauh lebih kecil daripada library lengkap (sekitar urutan yang lebih kecil) tetapi menghilangkan fitur tertentu seperti deskriptor dan refleksi. Hal ini sangat berguna untuk aplikasi yang berjalan pada platform terbatas seperti ponsel. Compiler akan tetap menghasilkan implementasi cepat dari semua metode seperti dalam modeSPEED
. Class yang dihasilkan hanya akan mengimplementasikan antarmukaMessageLite
dalam setiap bahasa, yang hanya menyediakan subset metode dari antarmukaMessage
lengkap.
option optimize_for = CODE_SIZE;
cc_generic_services
,java_generic_services
,py_generic_services
(opsi file): Apakah compiler buffering protokol harus membuat kode layanan abstrak berdasarkan definisi layanan di C++, Java, dan Python. Untuk alasan lama, setelan defaultnya adalahtrue
. Namun, mulai versi 2.3.0 (Januari 2010), dianggap lebih baik bagi penerapan RPC untuk menyediakan plugin generator kode untuk menghasilkan kode yang lebih spesifik untuk setiap sistem, daripada mengandalkan layanan "abstrak".// This file relies on plugins to generate service code. option cc_generic_services = false; option java_generic_services = false; option py_generic_services = false;
cc_enable_arenas
(opsi file): Mengaktifkan alokasi Arena untuk kode yang dihasilkan C++.message_set_wire_format
(opsi pesan): Jika ditetapkan ketrue
, pesan akan menggunakan format biner berbeda yang dimaksudkan agar kompatibel dengan format lama yang digunakan dalam Google yang disebutMessageSet
. Pengguna di luar Google mungkin tidak perlu menggunakan opsi ini. Pesan harus dideklarasikan persis seperti berikut:message Foo { option message_set_wire_format = true; extensions 4 to max; }
packed
(opsi kolom): Jika ditetapkan ketrue
pada kolom berulang dengan jenis numerik dasar, encoding yang lebih ringkas akan digunakan. Tidak ada kerugian untuk menggunakan opsi ini. Namun, perlu diperhatikan bahwa sebelum versi 2.3.0, parser yang menerima data yang dipaketkan saat tidak diperkirakan akan mengabaikannya. Oleh karena itu, kolom yang ada tidak dapat diubah ke format dikemas tanpa memutuskan kompatibilitas kabel. Pada versi 2.3.0 dan yang lebih baru, perubahan ini aman, karena parser untuk kolom yang dapat dikemas akan selalu menerima kedua format, tetapi berhati-hatilah jika Anda harus menangani program lama menggunakan versi protobuf lama.repeated int32 samples = 4 [packed = true];
deprecated
(opsi kolom): Jika ditetapkan ketrue
, menunjukkan bahwa kolom tersebut tidak digunakan lagi dan tidak boleh digunakan oleh kode baru. Di sebagian besar bahasa, hal ini tidak berpengaruh nyata. Di Java, anotasi ini menjadi anotasi@Deprecated
. Untuk C++, clang-tidy akan menghasilkan peringatan setiap kali kolom yang tidak digunakan lagi digunakan. Di masa mendatang, generator kode khusus bahasa lain dapat menghasilkan anotasi penghentian pada pengakses kolom, yang pada gilirannya akan menyebabkan peringatan dikeluarkan saat mengompilasi kode yang mencoba menggunakan kolom. Jika kolom tidak digunakan oleh siapa pun dan Anda tidak ingin pengguna baru menggunakannya, pertimbangkan untuk mengganti deklarasi kolom dengan pernyataan reservasi.optional int32 old_field = 6 [deprecated=true];
Opsi Kustom
Buffering Protokol bahkan memungkinkan Anda menentukan dan menggunakan opsi Anda sendiri. Perhatikan bahwa ini adalah fitur lanjutan yang tidak diperlukan sebagian besar orang. Karena opsi ditentukan oleh pesan yang ditentukan dalam google/protobuf/descriptor.proto
(seperti FileOptions
atau FieldOptions
), menentukan opsi Anda sendiri hanyalah masalah memperluas pesan tersebut. Contoh:
import "google/protobuf/descriptor.proto";
extend google.protobuf.MessageOptions {
optional string my_option = 51234;
}
message MyMessage {
option (my_option) = "Hello world!";
}
Di sini kita telah menentukan opsi tingkat pesan baru dengan memperluas MessageOptions
.
Jika kemudian kami menggunakan opsi tersebut, nama opsi harus diapit dalam tanda kurung untuk
menunjukkan bahwa ekstensi tersebut adalah ekstensi. Sekarang kita dapat membaca nilai my_option
di C++ seperti berikut:
string value = MyMessage::descriptor()->options().GetExtension(my_option);
Di sini, MyMessage::descriptor()->options()
menampilkan pesan protokol
MessageOptions
untuk MyMessage
. Membaca opsi kustom darinya sama seperti membaca ekstensi lainnya.
Demikian pula, di Java, kita akan menulis:
String value = MyProtoFile.MyMessage.getDescriptor().getOptions()
.getExtension(MyProtoFile.myOption);
Di Python, akan menjadi:
value = my_proto_file_pb2.MyMessage.DESCRIPTOR.GetOptions()
.Extensions[my_proto_file_pb2.my_option]
Opsi kustom dapat ditentukan untuk setiap jenis konstruksi dalam bahasa Protocol Buffer. Berikut adalah contoh yang menggunakan setiap jenis opsi:
import "google/protobuf/descriptor.proto";
extend google.protobuf.FileOptions {
optional string my_file_option = 50000;
}
extend google.protobuf.MessageOptions {
optional int32 my_message_option = 50001;
}
extend google.protobuf.FieldOptions {
optional float my_field_option = 50002;
}
extend google.protobuf.OneofOptions {
optional int64 my_oneof_option = 50003;
}
extend google.protobuf.EnumOptions {
optional bool my_enum_option = 50004;
}
extend google.protobuf.EnumValueOptions {
optional uint32 my_enum_value_option = 50005;
}
extend google.protobuf.ServiceOptions {
optional MyEnum my_service_option = 50006;
}
extend google.protobuf.MethodOptions {
optional MyMessage my_method_option = 50007;
}
option (my_file_option) = "Hello world!";
message MyMessage {
option (my_message_option) = 1234;
optional int32 foo = 1 [(my_field_option) = 4.5];
optional string bar = 2;
oneof qux {
option (my_oneof_option) = 42;
string quux = 3;
}
}
enum MyEnum {
option (my_enum_option) = true;
FOO = 1 [(my_enum_value_option) = 321];
BAR = 2;
}
message RequestType {}
message ResponseType {}
service MyService {
option (my_service_option) = FOO;
rpc MyMethod(RequestType) returns(ResponseType) {
// Note: my_method_option has type MyMessage. We can set each field
// within it using a separate "option" line.
option (my_method_option).foo = 567;
option (my_method_option).bar = "Some string";
}
}
Perhatikan bahwa jika ingin menggunakan opsi kustom dalam paket selain yang ditentukan, Anda harus mengawali nama opsi dengan nama paket, seperti yang Anda lakukan untuk nama jenis. Contoh:
// foo.proto
import "google/protobuf/descriptor.proto";
package foo;
extend google.protobuf.MessageOptions {
optional string my_option = 51234;
}
// bar.proto
import "foo.proto";
package bar;
message MyMessage {
option (foo.my_option) = "Hello world!";
}
Satu hal lagi: Karena opsi kustom adalah ekstensi, nomor kolom tersebut harus ditetapkan seperti kolom atau ekstensi lainnya. Dalam contoh di atas, kami telah menggunakan nomor kolom dalam rentang 50000-99999. Rentang ini hanya ditujukan untuk penggunaan internal dalam organisasi individual, sehingga Anda dapat menggunakan nomor dalam rentang ini secara bebas untuk aplikasi internal. Namun, jika Anda ingin menggunakan opsi kustom di aplikasi publik, Anda harus memastikan bahwa nomor kolom Anda unik secara global. Untuk mendapatkan nomor kolom yang unik secara global, harap kirim permintaan untuk menambahkan entri ke registrasi ekstensi global protobuf. Biasanya Anda hanya memerlukan satu nomor ekstensi. Anda dapat mendeklarasikan beberapa opsi hanya dengan satu nomor ekstensi dengan memasukkannya ke dalam sub-pesan:
message FooOptions {
optional int32 opt1 = 1;
optional string opt2 = 2;
}
extend google.protobuf.FieldOptions {
optional FooOptions foo_options = 1234;
}
// usage:
message Bar {
optional int32 a = 1 [(foo_options).opt1 = 123, (foo_options).opt2 = "baz"];
// alternative aggregate syntax (uses TextFormat):
optional int32 b = 2 [(foo_options) = { opt1: 123 opt2: "baz" }];
}
Selain itu, perhatikan bahwa setiap jenis opsi (tingkat file, tingkat pesan, tingkat kolom, dll.) memiliki ruang angkanya sendiri, jadi, misalnya, Anda dapat mendeklarasikan ekstensi FieldOptions dan MessageOptions dengan nomor yang sama.
Membuat Class Anda
Untuk membuat kode Java, Python, atau C++, Anda harus menggunakan jenis pesan yang ditentukan dalam file .proto
, Anda harus menjalankan compiler buffering protokol protoc
di .proto
. Jika compiler belum diinstal,
download paket dan ikuti petunjuk di
README.
Protocol Compiler dipanggil sebagai berikut:
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto
IMPORT_PATH
menentukan direktori yang akan digunakan untuk mencari file.proto
saat menyelesaikan perintahimport
. Jika dihilangkan, direktori saat ini akan digunakan. Beberapa direktori impor dapat ditentukan dengan meneruskan opsi--proto_path
beberapa kali; direktori tersebut akan ditelusuri secara berurutan.-I=_IMPORT_PATH_
dapat digunakan sebagai bentuk singkat dari--proto_path
.Anda dapat memberikan satu atau beberapa perintah output:
--cpp_out
menghasilkan kode C++ diDST_DIR
. Lihat referensi kode yang dihasilkan C++ untuk mengetahui informasi selengkapnya.--java_out
menghasilkan kode Java diDST_DIR
. Lihat referensi kode yang dihasilkan Java untuk mengetahui informasi selengkapnya.--python_out
menghasilkan kode Python diDST_DIR
. Lihat referensi kode yang dihasilkan Python untuk mengetahui informasi selengkapnya.
Untuk memudahkan, jika
DST_DIR
diakhiri dengan.zip
atau.jar
, compiler akan menulis output ke satu file arsip format ZIP dengan nama tertentu. Output.jar
juga akan diberi file manifes seperti yang diwajibkan oleh spesifikasi Java JAR. Perlu diperhatikan bahwa jika arsip output sudah ada, arsip tersebut akan ditimpa; compiler tidak cukup cerdas untuk menambahkan file ke arsip yang ada.Anda harus memberikan satu atau beberapa file
.proto
sebagai input. Beberapa file.proto
dapat ditentukan sekaligus. Meskipun diberi nama relatif terhadap direktori saat ini, setiap file harus berada di salah satuIMPORT_PATH
agar compiler dapat menentukan nama kanonisnya.
Lokasi file
Memilih untuk tidak menempatkan file .proto
dalam direktori yang sama dengan sumber bahasa
lain. Pertimbangkan untuk membuat proto
sub-paket untuk file .proto
, di bagian
paket root untuk project Anda.
Lokasi Tidak Harus Bahasa
Saat menangani kode Java, sebaiknya tempatkan file .proto
terkait dalam
direktori yang sama dengan sumber Java. Namun, jika kode non-Java pernah menggunakan
proto yang sama, awalan jalur tidak akan lagi berlaku. Jadi secara umum, tempatkan
proto di direktori yang tidak bergantung pada bahasa terkait seperti //myteam/mypackage
.
Pengecualian untuk aturan ini adalah ketika sudah jelas bahwa proto hanya akan digunakan dalam konteks Java, seperti untuk pengujian.
Platform yang Didukung
Untuk informasi tentang:
- sistem operasi, compiler, sistem build, dan versi C++ yang didukung, lihat Kebijakan Dukungan C++ Dasar.
- versi PHP yang didukung, lihat Versi PHP yang didukung.