Protobuf (Protocol Buffers) sangat populer sebagai format serialisasi lintas platform, hemat ruang dan cepat. Saat membangun protocol message antar service, kita sering dihadapkan pada kebutuhan satu field yang dapat berisi beberapa tipe data berbeda (mirip pola union atau sum type di bahasa lain). Di sinilah fitur powerful bernama oneof
menjadi senjata andalan. Artikel ini akan membahas pengaplikasian oneof
di Protobuf dengan contoh kode, simulasi, hingga diagram alur.
Kapasitas Ekspresif yang Lebih Tinggi
Dalam Protobuf, setiap field biasanya bertipe sederhana: string, int, message, enum. Namun, life isn’t always that simple! Kadang, sebuah respons API, event atau command butuh field yang bisa punya beberapa kemungkinan tipe sekaligus, misal, hasil bisa berupa error, data, atau status.
Property seperti ini sulit dicapai bila hanya mengandalkan beberapa optional field—dan yang terjadi, UI/UX di sisi konsumen akan chaos: “Field mana yang diisi, mana yang valid?”
Di bawah ini ilustrasi sederhana:
// Tidak dianjurkan!
message PlainResponse {
string error = 1;
Data data = 2;
Status status = 3;
}
Bingung, bukan? Apakah error
sedang diisi, atau data
, atau malah keduanya? Bisa saja ketiganya kosong.
Konsep oneof
: Elegan & Aman
Di sinilah fitur oneof
diperkenalkan oleh Protobuf untuk mewakili disjoint union:
message OneOfResponse {
oneof result {
string error = 1;
Data data = 2;
Status status = 3;
}
}
Dengan demikian, HANYA satu (atau tidak sama sekali) field yang boleh terisi dalam satu waktu. Protobuf akan memastikan kevalidan ini saat encoding maupun decoding.
Contoh Kasus: Messaging System
Mari kita bawa ke skenario dunia nyata, misal pada sistem pesan sederhana (chat), dua jenis pesan utama adalah teks dan gambar. Akan lebih masuk akal jika struktur pesan didefinisikan sebagai:
syntax = "proto3";
message TextMessage {
string text = 1;
}
message ImageMessage {
string url = 1;
string caption = 2;
}
message ChatMessage {
string sender = 1;
int64 timestamp = 2;
oneof content {
TextMessage text_msg = 3;
ImageMessage image_msg = 4;
}
}
Dengan definition di atas, konsumen dijamin hanya akan menerima satu tipe isi pesan dalam satu waktu—tidak mungkin terjadi pesan berisi teks dan gambar, atau malah dua-duanya null (kecuali memang pesan tanpa konten).
Simulasi: Implementasi di Golang
Mari simulasikan bagaimana kita ngobrol dengan Protobuf di Golang (dengan asumsi protoc
telah menghasilkan kode Go):
chat := &messaging.ChatMessage{
Sender: "Alicia",
Timestamp: time.Now().Unix(),
Content: &messaging.ChatMessage_TextMsg{
TextMsg: &messaging.TextMessage{
Text: "Selamat pagi!",
},
},
}
bin, err := proto.Marshal(chat)
// ... send over gRPC or save to DB
// Deserialize
var recv messaging.ChatMessage
err = proto.Unmarshal(bin, &recv)
switch content := recv.Content.(type) {
case *messaging.ChatMessage_TextMsg:
fmt.Println("Pesan berupa teks:", content.TextMsg.Text)
case *messaging.ChatMessage_ImageMsg:
fmt.Println("Pesan berupa gambar:", content.ImageMsg.Url)
default:
fmt.Println("Unknown message type")
}
Penggunaan oneof
membuat field content
bertipe interface, sehingga dengan idiom Go-style switch, sangat mudah menapis tipe data sebenarnya.
Tabel Komparatif: Classical Optional Fields VS. oneof
Kriteria | Optional Fields | oneof |
---|---|---|
Validasi satu field isi | Harus dilakukan manual | Otomatis oleh Protobuf |
Kemudahan parsing di code | Lebih rumit, banyak if-else | Langsung pattern match/switch |
Ukuran on wire/protobuf | Semua fields bisa terisi/sampah | Hanya satu field ter-encode |
Model representasi tipe | Redundant, ambigu | Mirip enum/disjoin-union |
Diagram Alur Proses Serialisasi
Mari kita visualisasikan alur serialisasi oneof
berikut:
flowchart TD A(User Membuat ChatMessage) --> B{Isi Field content?} B -- TextMessage --> C1[Isi field text_msg] B -- ImageMessage --> C2[Isi field image_msg] B -- Kosong --> D[Simpan message tanpa content] C1 --> E[Protobuf Serialize] C2 --> E D --> E E --> F[Kirim via network/gRPC]
Manfaat Sampingan
- Evolusi Schema: Menambah tipe baru cukup menambahkan satu
oneof
, tanpa menggangu consumer. - Interoperabilitas: Membantu protobuf consumer lintas bahasa mengerti semantik dengan tepat.
- Compatibility: Struktur semacam ini memberikan backward-compatibility yang baik (asalkan jangan ubah/rename field number!).
Tips & Best Practice
- Always Use Named oneof
Nama jelas (misalcontent
,result
) membuat schema mudah dibaca. - Dokumentasikan Setiap Anggota
Tambahkan deskripsi pada setiap field untuk menghindari misuse. - Gunakan Versi
Jika ingin menambah tipe data di masa depan, gunakan field number baru. - Referensi ke sub-message
Hindari menyimpan banyak primitive berbeda di dalam satuoneof
.
Anti-pattern: Nested oneof
Overuse
Jangan terlalu dalam menanamkan oneof
secara rekursif kecuali memang ada use case spesifik. Hal ini akan membuat pesan sulit di-debug dan dipelihara.
Kesimpulan
Protobuf oneof
membawa konsep neu-union ke level pengembangan microservices, API, hingga sistem message queue. Ia mencegah munculnya pola field saling tumpang tindih, menjaga model data tetap semantik, dan mudah diparsing. Jika Anda sedang mendesain protocol yang membutuhkan “field yang hanya boleh diisi satu”, jangan ragu untuk memakai oneof
.
Makin sering memanfaatkan pola ini di protobuf, codebase Anda akan makin bersih, robust, dan future-proof.
Bonus:
Sumber dokumentasi resmi Protobuf oneof.
Selamat bereksperimen, dan semoga arsitektur protokol Anda semakin mantap! 🚀
Referensi
- https://protobuf.dev/programming-guides/proto3/#oneof
- https://github.com/protocolbuffers/protobuf
- https://developers.google.com/protocol-buffers/docs/proto3#oneof
20 Mengelola Query dengan Parameter di graphql-go
Artikel Terhangat
22 Membuat Sub-Schema Berdasarkan Modul
07 Jul 2025
44 Reuse Message dengan `import`
07 Jul 2025
43 Memanfaatkan `oneof` di Protobuf
07 Jul 2025
42 Menggunakan Enum di Protobuf
07 Jul 2025
19 Implementasi Resolver untuk Query `users`
07 Jul 2025
41 Memahami Nested Messages
07 Jul 2025

22 Membuat Sub-Schema Berdasarkan Modul

44 Reuse Message dengan `import`

43 Memanfaatkan `oneof` di Protobuf

42 Menggunakan Enum di Protobuf

19 Implementasi Resolver untuk Query `users`
