CATATAN: Situs ini tidak digunakan lagi. Situs akan dinonaktifkan setelah 31 Januari 2023, dan traffic akan dialihkan ke situs baru di https://protobuf.dev. Sementara itu, perubahan hanya akan dibuat untuk protobuf.dev.

Panduan Bahasa

Tetap teratur dengan koleksi Simpan dan kategorikan konten berdasarkan preferensi Anda.

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 class Builder 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 atau repeated. Artinya, setiap pesan yang diserialisasi berdasarkan kode menggunakan format pesan "lama" dapat diurai oleh kode baru yang dihasilkan, karena elemen tersebut tidak akan kehilangan elemen required. 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, dan bool 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 dan sint64 kompatibel satu sama lain, tetapi tidak kompatibel dengan jenis bilangan bulat lainnya.
  • string dan bytes kompatibel selama byte-nya adalah UTF-8 yang valid.
  • Pesan tersemat kompatibel dengan bytes jika byte berisi versi pesan yang dienkode.
  • fixed32 kompatibel dengan sfixed32, dan fixed64 dengan sfixed64.
  • Untuk kolom string, bytes, dan pesan, optional kompatibel dengan repeated. Dengan mempertimbangkan data serial kolom berulang sebagai input, klien yang mengharapkan kolom ini menjadi optional 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 kolom optional 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 dengan int32, uint32, int64, dan uint64 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, nilai enum yang tidak dikenal akan dihapus saat pesan dideserialisasi, yang membuat pengakses has.. kolom menampilkan nilai salah dan pengambilnya akan menampilkan nilai pertama yang tercantum dalam definisi enum, 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 ke enum 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 anggota oneof 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 ke oneof baru mungkin aman jika Anda yakin bahwa tidak ada kode yang menyetel lebih dari satu per satu. Memindahkan kolom ke oneof yang ada tidak aman. Demikian juga, mengubah satu kolom oneof menjadi kolom atau ekstensi optional aman.
  • Mengubah kolom antara map<K, V> dan kolom pesan repeated 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 kolom repeated akan menghasilkan hasil identik secara semantik; namun, klien yang menggunakan definisi kolom map 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 metode set_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 memiliki sub_message dan msg2 akan memiliki name.

    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, atau required.
  • 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 namespace foo::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 aturan go_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 opsi java_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 ada java_outer_classname eksplisit yang ditentukan dalam file .proto, nama class akan dibuat dengan mengonversi nama file .proto menjadi camel-case (sehingga foo_bar.proto menjadi FooBar.java). Jika opsi java_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 (lihat java_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 ke SPEED, CODE_SIZE, atau LITE_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 dengan SPEED, tetapi operasinya akan lebih lambat. Class masih akan menerapkan API publik yang sama persis seperti yang ada di mode SPEED. 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 bukan libprotobuf). 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 mode SPEED. Class yang dihasilkan hanya akan mengimplementasikan antarmuka MessageLite dalam setiap bahasa, yang hanya menyediakan subset metode dari antarmuka Message 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 adalah true. 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 ke true, pesan akan menggunakan format biner berbeda yang dimaksudkan agar kompatibel dengan format lama yang digunakan dalam Google yang disebut MessageSet. 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 ke true 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 ke true, 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 perintah import. 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:

    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 satu IMPORT_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: