
Halo, para developer! Pernah nggak sih kamu mikir, gimana caranya aplikasi-aplikasi modern itu bisa saling kirim data dengan cepat dan efisien, bahkan antar bahasa pemrograman yang beda-beda? Salah satu rahasianya ada di Protocol Buffers, atau yang sering kita sebut Protobuf. Di api.co.id, blog seputar tech dan programming, kami selalu berusaha memberikan insight terbaru buat kamu. Nah, artikel kali ini kita bakal bedah tuntas tentang syntax Protobuf. Ini penting banget karena dari sinilah semua definisi data kamu dibuat. Dengan memahami cara menulis file .proto yang benar, kamu bisa bikin komunikasi data yang robust, hemat bandwidth, dan gampang dikembangkan di masa depan.
Mungkin kamu sudah pernah dengar Protobuf itu keren, cepat, dan efisien. Tapi, gimana sih cara ‘ngomong’ sama Protobuf biar dia bisa paham struktur data yang mau kita pakai? Jawabannya ada di syntax-nya! Ibarat kamu mau bangun rumah, kamu butuh denah yang jelas, kan? Nah, file .proto ini adalah denah data kamu. Yuk, kita selami lebih dalam biar kamu nggak cuma tahu Protobuf itu apa, tapi juga jago nulis definisinya!

Kenapa Syntax Protobuf Itu Penting Banget buat Kamu?
Sebelum kita terjun ke detail teknis, penting untuk paham kenapa belajar syntax Protobuf ini krusial. Protobuf itu intinya adalah bahasa deskripsi data agnostik bahasa. Artinya, kamu bisa definisiin struktur data sekali pakai syntax Protobuf, terus dari definisi itu, Protobuf bakal generate kode untuk berbagai bahasa pemrograman (Java, Python, C++, Go, dll.). Ini yang bikin dia super fleksibel dan powerful. Tanpa pemahaman yang kuat tentang syntax-nya, kamu bakal kesulitan untuk mendefinisikan data yang kompleks, menjaga kompatibilitas, dan memanfaatkan semua keunggulan Protobuf.
Bayangin aja kalau kamu punya backend pakai Go dan frontend pakai React (JavaScript). Dengan Protobuf, kamu cukup definisikan format request/response sekali aja di file .proto. Nanti, Protobuf generator yang akan buatkan kelas atau struct yang sesuai di Go dan JavaScript. Ini menghemat banyak waktu dan mengurangi error karena definisi data jadi terpusat dan konsisten.
related article: Mengenal Protobuf gRPC: Panduan Lengkap untuk Developer
Anatomi File .proto: Basic Building Blocks-nya Protobuf
Setiap kali kita mau mendefinisikan struktur data di Protobuf, kita bakal nulisnya di dalam file dengan ekstensi .proto. File ini akan jadi ‘cetak biru’ data kita. Ada beberapa elemen dasar yang selalu ada atau sering kita temukan di file .proto. Mari kita bedah satu per satu.
1. Deklarasi Versi Protobuf
Ini adalah hal pertama yang harus kamu lakukan di setiap file .proto. Kenapa? Karena Protobuf punya dua versi utama yang paling sering dipakai: proto2 dan proto3. Keduanya punya sedikit perbedaan di syntax dan fitur. Hampir semua project modern sekarang pakai proto3 karena lebih sederhana dan punya fitur yang lebih baik. Untuk mendeklarasikannya, cukup tulis ini di baris pertama:
syntax = "proto3";
Tanpa baris ini, Protobuf compiler (protoc) akan secara default menganggap file kamu adalah proto2, dan itu bisa menyebabkan error atau perilaku yang tidak terduga kalau kamu berniat pakai fitur proto3.
2. Package
Sama seperti di banyak bahasa pemrograman lain, package di Protobuf berfungsi untuk mencegah konflik nama antara tipe data yang berbeda, terutama saat kamu punya banyak file .proto atau mengimpor file dari pihak ketiga. Ini juga akan memengaruhi struktur folder atau namespace saat kode digenerate. Contohnya:
package com.api.blog.user;
Jika kamu pakai Java, ini akan jadi nama package Java-nya. Kalau di Go, ini akan memengaruhi jalur impor. Gunakan nama package yang deskriptif dan mengikuti konvensi penamaan yang baik.
3. Import Lainnya
Kadang, kamu perlu menggunakan definisi message atau enum dari file .proto lain. Untuk itu, kamu bisa pakai keyword import. Ini mirip seperti import di Python atau include di C++. Misalnya:
import "google/protobuf/timestamp.proto"; // Contoh import dari standar Protobuf
Ini memungkinkan modularitas. Jadi, kamu nggak perlu nulis ulang definisi yang sama di banyak tempat. Kamu bisa membuat file .proto kecil yang fokus pada satu domain, lalu mengimpornya ke file lain yang membutuhkan.
4. Options
Keyword option digunakan untuk memberikan metadata tambahan ke file .proto, message, field, atau service. Metadata ini dipakai oleh Protobuf compiler untuk memengaruhi bagaimana kode digenerate. Contoh yang umum adalah:
java_package: Menentukan package Java yang akan digunakan, jika berbeda daripackageProtobuf.option java_package = "com.api.blog.generated";java_outer_classname: Menentukan nama kelas outer di Java.option java_outer_classname = "UserProto";go_package: Menentukan import path dan nama package Go.option go_package = "github.com/api/blog/user;user";
Options ini sangat berguna untuk menyesuaikan output kode yang digenerate agar sesuai dengan konvensi atau kebutuhan spesifik di bahasa pemrograman target kamu.
Tipe Data Protobuf: Fondasi Data Kamu
Sekarang, kita masuk ke bagian inti, yaitu tipe data. Protobuf punya set tipe data skalar yang efisien dan bisa memetakan ke tipe data di hampir semua bahasa pemrograman. Ini dia daftarnya:
double: Bilangan floating-point presisi ganda (64-bit).float: Bilangan floating-point presisi tunggal (32-bit).int32: Integer 32-bit. Efisien untuk nilai negatif, tapi bisa kurang efisien untuk nilai positif besar.int64: Integer 64-bit.uint32: Integer tak bertanda 32-bit.uint64: Integer tak bertanda 64-bit.sint32: Integer 32-bit bertanda. Lebih efisien daripadaint32untuk nilai negatif karena menggunakan zigzag encoding.sint64: Integer 64-bit bertanda.fixed32: Integer 32-bit tak bertanda. Ukuran tetap 4 byte. Lebih cepat untuk di-serialize/deserialize tapi kurang efisien untuk nilai kecil.fixed64: Integer 64-bit tak bertanda. Ukuran tetap 8 byte.sfixed32: Integer 32-bit bertanda. Ukuran tetap 4 byte.sfixed64: Integer 64-bit bertanda. Ukuran tetap 8 byte.bool: Boolean (true/false).string: Urutan karakter UTF-8. String di Protobuf itu immutable.bytes: Urutan byte mentah. Cocok untuk data biner seperti gambar, audio, atau hash.
Pemilihan tipe data yang tepat sangat memengaruhi ukuran data yang diserialisasi dan performa. Misalnya, kalau kamu tahu angka kamu selalu positif dan kecil, pakai uint32 atau sint32 akan lebih efisien daripada int32. Tapi, kalau presisi sangat penting dan kamu butuh perhitungan floating point, double atau float jelas jadi pilihan.
related article: Mengenal JSON Web Tokens (JWT): Keamanan API Stateless
Message: Struktur Data Kustom Kamu
Ini adalah jantung dari definisi data di Protobuf. message adalah cara kamu mendefinisikan struktur data kustom yang kompleks, mirip dengan kelas atau struct di bahasa pemrograman lain. Sebuah message terdiri dari satu atau lebih fields.
Mendefinisikan Message
Begini cara kamu mendefinisikan sebuah message:
message User {
int32 id = 1;
string name = 2;
string email = 3;
}
Di sini, User adalah nama message-nya. Di dalamnya ada tiga fields: id, name, dan email. Setiap field punya tipe data (int32, string) dan yang paling penting, nomor identifikasi unik.
Field Numbering: Jangan Sampai Salah!
Nomor identifikasi di setiap field (misalnya id = 1, name = 2) itu krusial banget dan tidak boleh ada yang duplikat dalam satu message. Nomor ini digunakan Protobuf untuk mengidentifikasi field saat data diserialisasi dan deserialisasi. Ini beberapa aturan penting:
- Nomor harus unik dalam satu
message. - Nomor 1 sampai 15 membutuhkan 1 byte untuk encoding. Nomor 16 sampai 2047 membutuhkan 2 byte. Jadi, untuk field yang sering diakses atau wajib, alokasikan nomor kecil (1-15) untuk efisiensi.
- Nomor 19000 sampai 19999 dicadangkan oleh Protobuf dan tidak boleh digunakan.
- Setelah field diberi nomor, nomor itu jangan diubah atau digunakan lagi untuk field lain, bahkan jika field asli sudah dihapus. Ini penting untuk menjaga backward compatibility.
Maksudnya backward compatibility itu gini: kalau kamu mengubah nomor field atau mendefinisikan ulang field dengan nomor yang sudah dipakai sebelumnya, aplikasi lama yang masih pakai definisi .proto lama bakal error saat mencoba membaca data dari aplikasi baru, atau sebaliknya. Jadi, pikirkan matang-matang penomoran ini!
Field Rules (di proto3, ini lebih sederhana)
Di proto3, semua field secara default itu optional (opsional). Artinya, mereka bisa tidak diset, dan saat diserialisasi, mereka akan diabaikan jika tidak memiliki nilai. Ini berbeda dengan proto2 yang punya required, optional, dan repeated. Di proto3, ada dua kategori utama:
- Scalar fields (tanpa keyword khusus): Ini adalah field opsional biasa. Jika tidak diisi, ia akan memiliki nilai default (0 untuk angka, false untuk boolean, string kosong untuk string, byte kosong untuk bytes).
repeated: Digunakan untuk mendefinisikan list atau array dari suatu tipe data. Contohnya:message UserList { repeated User users = 1; // List user }Ini berarti
usersadalah daftar dari objekUser. Saat digenerate, ini akan menjadiList<User>di Java atau[]Userdi Go.
Enum: Pilihan Nilai yang Terbatas
Kadang, kamu butuh sebuah field yang nilainya hanya boleh salah satu dari beberapa pilihan yang sudah ditentukan. Di sinilah enum berperan. enum memungkinkan kamu mendefinisikan sekumpulan konstanta bernama.
enum UserStatus { UNKNOWN = 0; ACTIVE = 1; INACTIVE = 2; PENDING = 3;}
message User { int32 id = 1; string name = 2; UserStatus status = 3; // Menggunakan enum}
- Setiap
enumharus punya elemen pertama dengan nilai 0. Ini adalah nilai default yang akan digunakan jika fieldenumtidak diset. - Nilai
enumjuga harus unik.
Dengan enum, kamu bisa memastikan konsistensi data dan menghindari error karena nilai yang tidak valid.
related article: Apa Itu WSDL? “Buku Menu” Wajib bagi Web Service Berbasis SOAP
Oneof: Field yang Mutually Exclusive
Situasi lain yang sering muncul adalah ketika sebuah message bisa memiliki salah satu dari beberapa field, tapi tidak bisa memiliki semuanya secara bersamaan. Contohnya, sebuah pembayaran bisa pakai kartu kredit ATAU e-wallet, tapi bukan keduanya sekaligus. Di sinilah oneof sangat berguna.
message Payment {
oneof payment_method {
CreditCardInfo credit_card = 1;
EWalletInfo e_wallet = 2;
}
int64 amount = 3;
}
message CreditCardInfo {
string card_number = 1;
string expiry_date = 2;
}
message EWalletInfo {
string provider = 1;
string phone_number = 2;
}
Dalam contoh di atas, Payment bisa memiliki credit_card ATAU e_wallet, tapi tidak keduanya. Jika kamu set credit_card, lalu kemudian set e_wallet, maka credit_card akan otomatis di-clear. Ini memastikan konsistensi logika data kamu.
Map: Key-Value Pairs
Di proto3, kamu bisa mendefinisikan field bertipe map, yang mirip dengan hash map atau dictionary di bahasa pemrograman. map punya pasangan key-value.
message UserMetadata {
map<string, string> tags = 1;
map<int32, string> settings = 2;
}
- Key dari
mapbisa berupa tipe data skalar Protobuf (integer, boolean, string), tapi tidak bisa berupafloatataudouble. - Value dari
mapbisa berupa tipe data Protobuf apa pun, termasukmessagelain atauenum. - Elemen
maptidak dijamin urutannya.
related article: Mengenal JSON: Format Data ‘Ringan’ yang Menjadi Pasangan Setia REST API
Nesting Messages dan Enums: Organisasi yang Rapi
Untuk struktur data yang lebih kompleks, kamu bisa mendefinisikan message atau enum di dalam message lain. Ini membantu menjaga kebersihan dan organisasi kode kamu, mirip seperti nested class.
message OuterMessage {
message InnerMessage { string value = 1; }
InnerMessage inner_field = 1;
enum Status { UNKNOWN = 0; ACTIVE = 1; }
Status outer_status = 2;
}
Ini sangat berguna ketika sebuah sub-struktur data hanya relevan dalam konteks message tertentu. Kamu juga bisa merujuk ke InnerMessage dari luar OuterMessage dengan menggunakan nama lengkapnya, misalnya OuterMessage.InnerMessage.
Service Definitions: Jembatan ke Komunikasi RPC
Salah satu aplikasi paling powerful dari Protobuf adalah kombinasinya dengan gRPC. Protobuf tidak hanya mendefinisikan struktur data, tapi juga bisa mendefinisikan service (layanan) untuk komunikasi Remote Procedure Call (RPC). Kalau kamu sudah akrab dengan REST API, gRPC ini adalah alternatif yang lebih modern dan efisien.
Di file .proto, kamu bisa mendefinisikan antarmuka layanan (service interface) dengan metode-metode yang bisa dipanggil. Setiap metode akan memiliki tipe request dan response yang juga didefinisikan sebagai message Protobuf.
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc CreateUser(CreateUserRequest) returns (User);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
}
message GetUserRequest {
int32 user_id = 1;
}
message CreateUserRequest {
string name = 1;
string email = 2;
}
message ListUsersRequest {
int32 page_size = 1;
string page_token = 2;
}
message ListUsersResponse {
repeated User users = 1;
string next_page_token = 2;
}
Dengan definisi service ini, protoc (Protobuf compiler) bisa generate kode client dan server boilerplate untuk gRPC di berbagai bahasa. Ini adalah salah satu alasan kenapa Protobuf sering banget dipakai bareng gRPC.
Untuk pemahaman lebih lanjut tentang bagaimana Protobuf bersinergi dengan gRPC secara keseluruhan, kamu bisa cek artikel kami di api.co.id yang membahasnya secara lengkap.
related article: Strategi Migrasi Monolith ke Microservice yang Efektif
Well-Known Types: Mempermudah Pekerjaan Kamu
Google menyediakan sekumpulan message Protobuf standar yang disebut “Well-Known Types”. Ini adalah definisi umum untuk tipe-tipe data yang sering muncul di banyak aplikasi, seperti tanggal dan waktu, durasi, wrapper untuk nilai null, dan lain-lain. Menggunakan ini sangat disarankan daripada membuat definisi sendiri, karena:
- Konsistensi: Menjamin semua orang pakai format yang sama.
- Kompatibilitas: Mudah berinteraksi dengan layanan lain yang juga menggunakan Well-Known Types.
- Efisiensi: Sudah dioptimalkan oleh Google.
Contoh Well-Known Types yang sering dipakai:
google.protobuf.Timestamp: Untuk merepresentasikan tanggal dan waktu. Ini lebih baik daripada menyimpan tanggal sebagai string atau int.import "google/protobuf/timestamp.proto"; message Event { string name = 1; google.protobuf.Timestamp event_time = 2; }google.protobuf.Duration: Untuk merepresentasikan durasi waktu.google.protobuf.StringValue,Int32Value, dll.: Untuk field opsional yang bisa null. Diproto3, tipe data skalar secara default tidak bisa null, hanya punya nilai default. Wrapper ini memungkinkan kamu untuk secara eksplisit merepresentasikan “tidak ada nilai” vs “nilai default”.
Best Practices dan Tips untuk Syntax Protobuf yang Bersih
Menulis file .proto itu nggak cuma soal tahu syntax-nya aja, tapi juga soal bagaimana menuliskannya dengan rapi, mudah dibaca, dan yang paling penting, bisa berkembang di masa depan. Ini beberapa tips dari kami:
- Konsisten dalam Penamaan: Gunakan
CamelCaseuntuk namamessagedanenum(misal:UserProfile,UserStatus). Gunakansnake_caseuntuk namafielddanenum value(misal:user_id,ACTIVE_STATUS). - Komentar yang Jelas: Gunakan komentar (diawali dengan
//) untuk menjelaskan tujuanmessage,field, atauenumyang kompleks. Ini bantu banget buat orang lain (dan diri kamu sendiri di masa depan) yang membaca file.proto.// UserProfile merepresentasikan data profil pengguna di sistem. message UserProfile { // id_pengguna adalah ID unik untuk setiap user. int32 user_id = 1; // email_pengguna adalah alamat email user dan harus unik. string email_address = 2; } - Prioritaskan Backward Compatibility: Ini adalah aturan emas dalam Protobuf. Jangan pernah mengubah nomor field yang sudah ada atau menggunakan kembali nomor field yang sudah dihapus. Jika kamu menghapus field, sisakan nomornya sebagai cadangan dan tambahkan komentar bahwa nomor itu sudah tidak dipakai. Jika kamu ingin menambahkan field baru, tambahkan di akhir dan pastikan ia punya nilai default yang masuk akal.
- Gunakan Nomor Field Kecil untuk Field yang Sering Digunakan: Untuk efisiensi ukuran data, coba alokasikan nomor 1-15 untuk field yang paling sering diset atau paling penting.
- Modularitas dengan Import: Pecah file
.protokamu ke dalam beberapa file yang lebih kecil dan fokus pada satu domain atau entitas. Lalu, gunakanimportuntuk menggabungkannya. Ini bikin file kamu lebih mudah dikelola. - Manfaatkan Well-Known Types: Jangan bikin ulang roda! Jika ada Well-Known Type yang sesuai, pakai itu. Misalnya
google.protobuf.Timestampuntuk tanggal dan waktu. - Hindari Field
OptionaldenganWrapper TypesKecuali Benar-benar Perlu: Diproto3, semua field non-repeatedsudah opsional. Untuk tipe skalar, mereka akan punya nilai default jika tidak diset. Kadang ada kasus di mana kamu perlu membedakan antara ‘tidak diset’ dan ‘diset ke nilai default’ (misalnya, angka 0 vs tidak ada angka). Di sinilahgoogle.protobuf.Int32Valuedkk. bisa berguna. Tapi, jangan berlebihan karena bisa menambah verbositas. - Ubah dari Proto2 ke Proto3 (jika Masih Pakai Proto2): Proto3 lebih sederhana, punya fitur yang lebih baik, dan jadi standar di ekosistem Protobuf/gRPC modern. Jika memungkinkan, migrasikan definisi Proto2 kamu.
Kesimpulan: Kuasai Syntax, Kuasai Data Efisien!
Nah, sekarang kamu sudah punya gambaran lengkap tentang syntax Protobuf, mulai dari struktur dasar file .proto, berbagai tipe data skalar, cara membuat message yang kompleks, penggunaan enum, oneof, map, sampai definisi service untuk komunikasi RPC. Memahami syntax ini bukan cuma soal hafal keyword, tapi juga memahami filosofi di baliknya: efisiensi, konsistensi, dan interoperabilitas.
Dengan menguasai syntax Protobuf, kamu punya alat yang super powerful untuk mendefinisikan data di project-project kamu, terutama kalau kamu bekerja di lingkungan microservices atau membutuhkan komunikasi data yang sangat efisien dan multi-platform. Ingat, practice makes perfect! Coba deh mulai bikin file .proto sendiri, eksperimen dengan berbagai tipe data dan struktur, dan rasakan sendiri kemudahan yang ditawarkan Protobuf. Selamat ngoding, ya!






