tutorial

43 Memanfaatkan oneof di Protobuf

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

KriteriaOptional Fieldsoneof
Validasi satu field isiHarus dilakukan manualOtomatis oleh Protobuf
Kemudahan parsing di codeLebih rumit, banyak if-elseLangsung pattern match/switch
Ukuran on wire/protobufSemua fields bisa terisi/sampahHanya satu field ter-encode
Model representasi tipeRedundant, ambiguMirip 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

  1. Always Use Named oneof
    Nama jelas (misal content, result) membuat schema mudah dibaca.
  2. Dokumentasikan Setiap Anggota
    Tambahkan deskripsi pada setiap field untuk menghindari misuse.
  3. Gunakan Versi
    Jika ingin menambah tipe data di masa depan, gunakan field number baru.
  4. Referensi ke sub-message
    Hindari menyimpan banyak primitive berbeda di dalam satu oneof.

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

comments powered by Disqus

Topik Terhangat

programming
221
tutorial
88
tips-and-trick
43
jaringan
28
hardware
11
linux
4
kubernetes
1