Protobuf breaking changes: apa yang bertahan di wire
5 menit baca
API REST berubah saat bentuk JSON berubah, dan sebagian besar bentuk itu terlihat di respons yang
bisa Anda baca di browser. API gRPC berubah saat berkas .proto berubah, dan format biner wire
milik Protocol Buffers punya aturan sendiri tentang apa yang bisa ditoleransi client yang tidak
ada hubungannya dengan apa yang dikatakan nama field. Dua penyuntingan yang terlihat sama kecilnya
di diff, menomori ulang sebuah field versus menambahkannya, jatuh di sisi berlawanan dari garis
yang breaking changes gambar secara umum: satu tidak terlihat bagi
setiap client yang ada, yang lain merusak semuanya sekaligus. Membedakan protobuf breaking changes
dari yang aman berarti membaca aturan format wire itu sendiri, bukan menebak dari bagaimana
perubahan itu terbaca di diff .proto.
Mengapa penomoran field lebih penting daripada nama field dalam Protobuf?
Karena format wire mengkodekan field berdasarkan nomor, bukan nama. Kode yang dihasilkan dalam
setiap bahasa membaca dan menulis nomor-nomor itu; nama field email di berkas .proto Anda
adalah kemudahan bagi manusia yang tidak pernah menyentuh byte biner yang dikirim lewat
jaringan. Mengganti nama field, email menjadi email_address, aman di wire biner selama
nomornya tetap sama, yang mengejutkan insinyur yang terbiasa dengan REST, di mana key JSON yang
diganti namanya adalah persis jenis perubahan yang merusak client. Pengecualiannya adalah kasus REST yang sama:
format ProtoJSON dan text menserialisasi nama, jadi
mengganti nama merusak transcoding JSON (grpc-gateway, misalnya), berkas text-format dan field
mask. Menomori ulang field yang sama,
mempertahankan nama tapi mengubah 1 menjadi 7, adalah kebalikannya persis: tidak terlihat
dalam review kode yang hanya menampilkan nama, dan itu merusak setiap pesan yang dikirim atau
diterima client sejak titik itu.
| Perubahan | Aman di wire | Mengapa |
|---|---|---|
| Mengganti nama field, mempertahankan nomornya | Biner ya, JSON dan text tidak | Pengkodean biner menggunakan nomor; ProtoJSON dan format text menggunakan nama |
| Mengubah nomor sebuah field | Tidak | Setiap pesan yang ada sekarang dibaca sebagai field yang salah |
| Menambah field baru dengan nomor baru | Ya | Client lama mengabaikan field yang tidak mereka kenali |
| Menghapus field, menggunakan ulang nomor lamanya untuk hal lain | Tidak | Data lama diterjemahkan ke field baru yang salah |
Mengubah tipe field secara tidak kompatibel (mis. int32 ke string) | Tidak | Pengkodean wire berbeda per tipe |
Apa yang membuat menghapus field berbeda dari melakukannya dalam respons JSON REST?
Nomornya menjadi radioaktif. Panduan resmi Protobuf
merekomendasikan menandai nomor field yang dihapus sebagai reserved alih-alih membiarkannya digunakan ulang, karena penggunaan ulang
adalah di mana kerusakan sebenarnya terjadi: client yang masih menjalankan kode yang dihasilkan
bulan lalu mengirim pesan menggunakan nomor lama field itu untuk makna lama, dan server, yang
sekarang mengharapkan nomor itu berarti sesuatu yang lain, salah menafsirkan data secara diam-diam
alih-alih menolaknya secara langsung. REST tidak punya jebakan setara, karena key JSON yang
dihapus hanya berhenti muncul; tidak ada cara bagi permintaan client lama untuk diam-diam
ditafsirkan ulang sebagai sesuatu yang lain. Berkas .proto dengan reserved 4, 9, 12; di atas
sebuah message adalah bekas luka permanen, dan itulah maksudnya: itu menghentikan nomor tersebut
diberikan ke field baru oleh seseorang yang tidak tahu sejarahnya.
message Invoice {
reserved 4; // dulu `legacy_customer_id`, dihapus 2026-06-01
reserved "legacy_customer_id"; // namanya juga, untuk JSON/text
string customer_id = 5;
string status = 6;
}
Apakah menambah field pernah membutuhkan entri changelog sama sekali?
Biasanya bukan entri breaking change, tapi sering kali entri biasa, karena “aman di wire” dan “tidak terlihat bagi pembaca yang peduli” adalah dua klaim berbeda. Menambah field ke pesan respons tidak membebani apa pun secara struktural, client lama menerjemahkan pesan dan mengabaikan field baru secara otomatis. Tapi seseorang yang membangun integrasi baru terhadap layanan itu tidak punya cara mengetahui field itu ada kecuali seseorang memberi tahunya, karena tidak ada yang di build sukses atau tes yang lolos membuat field opsional baru terlihat. Changelog API membahas secara umum apa yang dihutangi entri aditif kepada pembaca; alasan khusus gRPC untuk tetap menulis satu adalah tidak ada padanan untuk menjelajahi respons REST di debugger untuk menyadari bahwa key baru muncul.
Bagaimana ini berbeda dari apa yang dihadapi pemanggil GraphQL?
Aturan untuk penambahan sama, tapi eksposurnya berbeda. Deprecation skema GraphQL membahas model di mana client hanya menerima field yang secara eksplisit dimintanya, yang membuat perubahan aditif pada dasarnya tanpa risiko dan penghapusan menjadi satu-satunya bahaya nyata. Client gRPC, sebaliknya, menerima apa pun yang dikirim server dan menerjemahkan semuanya terhadap salinan skema hasil kompilasi mereka sendiri; paparan client tidak dibatasi oleh apa yang dimintanya, hanya oleh apa yang bisa dibaca kode yang dihasilkannya. Perbedaan itu penting untuk menulis changelog: entri GraphQL bisa secara wajar mengasumsikan bahwa client terlindungi dari field yang tidak mereka minta, dan entri gRPC sama sekali tidak bisa mengasumsikan itu.
Apakah memversikan layanan gRPC bekerja sama seperti /v1/, /v2/ REST?
Mekanismenya berbeda bahkan saat niatnya sama. Apa itu v1 dan v2 dalam API
REST membahas versi sebagai jalur URL paralel yang
melayani kontrak berbeda; layanan gRPC biasanya memversikan lewat nama paket di berkas .proto
itu sendiri, payments.v1.InvoiceService menjadi payments.v2.InvoiceService, yang mengubah nama
layanan yang sepenuhnya memenuhi syarat yang dipanggil client alih-alih segmen URL yang
dimintanya. Kedua pendekatan menyelesaikan masalah yang sama, membiarkan kontrak lama tetap
berfungsi sementara yang baru ada, tapi tim yang berasal dari latar belakang REST sering mencari
nomor versi di tempat yang salah dan melewatkan bahwa deklarasi paket sedang melakukan pekerjaan
itu.
Apa yang seharusnya benar-benar disebutkan entri changelog gRPC?
Pesannya, nomor field-nya, dan apakah itu aditif atau penghapusan yang membutuhkan migrasi, dalam
urutan kepentingan itu bagi pembaca yang memutuskan apakah harus bertindak. “Menambahkan
shipping_address (field 8) ke Order” memberi tahu integrator semua yang diperlukan untuk
memperbarui kode yang dihasilkan dan mulai menggunakannya. “Mencadangkan field 4 pada Invoice,
legacy_customer_id hilang” memberi tahunya untuk memeriksa apakah ada sesuatu di kode basisnya
yang masih membaca field itu, sesuatu yang tidak dikomunikasikan catatan gaya REST “menghapus
field dari respons” dengan urgensi yang sama, karena penghapusan REST hanya mengembalikan lebih
sedikit data sementara penggunaan ulang field Protobuf secara aktif merusaknya.
FAQ
Bisakah tipe field pernah diubah tanpa merusak format wire?
Hanya dalam grup kompatibel spesifik yang didokumentasikan Protobuf, seperti memperlebar int32
ke int64 dalam beberapa kasus. Perlakukan perubahan tipe apa pun sebagai breaking kecuali Anda
sudah memeriksanya terhadap tabel kompatibilitas Protobuf sendiri; mengasumsikan kompatibilitas
melalui analogi dengan sistem tipe suatu bahasa adalah bagaimana ini menjadi salah.
Apakah mendepresiasi field dalam Protobuf bekerja seperti direktif @deprecated GraphQL?
Mirip: Protobuf mendukung opsi field [deprecated = true] yang bisa ditampilkan tooling. Keduanya
tidak ditegakkan: server GraphQL tetap menjawab query untuk field yang di-deprecate, dan client
protobuf tetap mengkodekannya. Keduanya bersifat penasihat dan membutuhkan dukungan changelog yang sama.
Apakah menomori ulang pernah aman jika Anda mengontrol setiap client? Dalam sistem yang sepenuhnya tertutup, pada prinsipnya, tapi itu menghilangkan seluruh properti keamanan yang menjadi alasan keberadaan nomor field, dan “kami mengontrol setiap client” adalah klaim yang berhenti benar begitu build di-cache, deploy tertunda, atau client ditambahkan yang tidak ada yang ingat. Cadangkan nomornya alih-alih menggunakannya ulang, bahkan secara internal.
Apakah layanan gRPC membutuhkan halaman changelog seperti API REST publik?
Hanya jika tim eksternal mengonsumsinya tanpa membaca diff .proto secara langsung, tes “siapa
yang ada di sisi lain” yang sama yang diterapkan changelog API internal
secara umum. Layanan gRPC yang hanya dikonsumsi layanan lain dari tim yang sama sering bisa
melewati changelog formal demi riwayat commit, karena siapa pun yang membacanya sudah membuka
skemanya.
Klaim teknis dalam artikel ini belum ditinjau secara independen. Jika ada yang keliru, beri tahu kami dan kami akan memperbaikinya.