Protobuf breaking change'leri: wire'da neler hayatta kalır
5 dk okuma
Bir REST API’si bir JSON şekli değiştiğinde değişir, ve o şeklin çoğu bir tarayıcıda
okuyabileceğiniz yanıtta görünürdür. Bir gRPC API’si bir .proto dosyası değiştiğinde değişir, ve
Protocol Buffers’ın ikili wire formatının, alan adlarının söylediğiyle hiçbir ilgisi olmayan, bir
client’ın neye tahammül edebileceğine dair kendi kuralları vardır. Diff’te eşit derecede küçük
görünen iki düzenleme, bir alanı yeniden numaralandırmak ile bir tane eklemek, breaking
change’in genel olarak çizdiği bir çizginin karşıt taraflarına düşer:
biri mevcut her client için görünmezdir, diğeri hepsini bir anda bozar.
Protobuf breaking change’lerini güvenli olanlardan ayırmak, değişikliğin bir .proto diff’inde
nasıl okunduğuna göre tahmin yürütmek değil, wire formatının kendi kurallarını okumak demektir.
Protobuf’ta alan numaralandırması neden alan adından daha çok önemlidir?
Çünkü wire formatı alanları isme göre değil, numaraya göre kodlar. Her dildeki üretilen kod bu
numaraları okur ve yazar; .proto dosyanızdaki email alan adı, ağ üzerinden gönderilen ikili
baytlara hiç dokunmayan, insanlar için bir kolaylıktır. Bir alanı yeniden adlandırmak, email’i
email_address’e, numara aynı kaldığı sürece ikili wire’da güvenlidir, ki bu REST’e alışmış
mühendisleri şaşırtır, orada yeniden adlandırılmış bir JSON key’i tam olarak bir client’ı bozan
değişiklik türüdür. İstisna aynı REST durumudur:
ProtoJSON ve text formatları adı serileştirir, bu
yüzden yeniden adlandırma JSON transcoding’i (örneğin bir grpc-gateway), text-format dosyalarını
ve field mask’leri bozar. Aynı alanı yeniden numaralandırmak, adı koruyup 1’i 7’ye değiştirmek, tam
tersidir: sadece isimleri gösteren bir kod incelemesinde görünmezdir, ve o noktadan itibaren bir
client’ın gönderdiği veya aldığı her mesajı bozar.
| Değişiklik | Wire’da güvenli mi | Neden |
|---|---|---|
| Bir alanı yeniden adlandırmak, numarasını korumak | İkili evet, JSON ve text hayır | İkili kodlama numarayı kullanır; ProtoJSON ve text formatı adı kullanır |
| Bir alanın numarasını değiştirmek | Hayır | Mevcut her mesaj artık yanlış alan olarak okunur |
| Yeni numarayla yeni bir alan eklemek | Evet | Eski client’lar tanımadıkları alanları görmezden gelir |
| Bir alanı kaldırmak, eski numarasını başka bir şey için yeniden kullanmak | Hayır | Eski veri yanlış yeni alana kod çözülür |
Bir alanın tipini uyumsuz şekilde değiştirmek (örn. int32’den string’e) | Hayır | Wire kodlaması tipe göre farklılık gösterir |
Bir alanı kaldırmayı REST JSON yanıtında yapmaktan farklı kılan nedir?
Numara radyoaktif hale gelir. Protobuf’ın kendi rehberliği, kaldırılan bir alanın numarasını
yeniden kullanılmasına izin vermek yerine reserved olarak işaretlemeyi önerir, çünkü gerçek
hasarın olduğu yer yeniden kullanımdır: geçen ayın üretilen kodunu hâlâ çalıştıran bir client,
alanın eski numarasını eski anlamı için kullanarak bir mesaj gönderir, ve artık o numaranın başka
bir şey ifade etmesini bekleyen sunucu, veriyi tamamen reddetmek yerine sessizce yanlış
yorumlar. REST’te eşdeğer bir tuzak yoktur, çünkü kaldırılan bir JSON key’i basitçe görünmeyi
bırakır; eski bir client’ın isteğinin sessizce başka bir şey olarak yeniden yorumlanmasının bir
yolu yoktur. Bir mesajın en üstünde reserved 4, 9, 12; olan bir .proto dosyası kalıcı bir
yaradır, ve amaç budur: numaranın, tarihini bilmeyen biri tarafından yeni bir alana verilmesini
engeller.
message Invoice {
reserved 4; // eskiden `legacy_customer_id`, 2026-06-01'de silindi
reserved "legacy_customer_id"; // adı da, JSON/text için
string customer_id = 5;
string status = 6;
}
Bir alan eklemek hiç bir changelog kaydı gerektirir mi?
Genellikle breaking change kaydı değil, ama sıklıkla normal bir kayıt gerektirir, çünkü “wire’da güvenli” ile “önemseyen bir okuyucu için görünmez” iki farklı iddiadır. Bir yanıt mesajına alan eklemek yapısal olarak hiçbir şeye mal olmaz, eski client’lar mesajı kod çözer ve yeni alanı otomatik olarak görmezden gelir. Ama o servise karşı yeni bir entegrasyon kuran biri, birisi ona söylemedikçe alanın var olduğunu bilmenin bir yolunu bulamaz, çünkü başarılı bir build veya geçen bir testte yeni bir opsiyonel alanı görünür kılan hiçbir şey yoktur. API changelog genel olarak eklemeli bir kaydın okuyuculara ne borçlu olduğunu ele alır; yine de bir tane yazmanın gRPC’ye özgü nedeni, bir hata ayıklayıcıda REST yanıtına göz atarak yeni bir key’in ortaya çıktığını fark etmenin bir eşdeğerinin olmamasıdır.
Bu, GraphQL çağıranların uğraştığı şeyden nasıl farklıdır?
Eklemeler için kurallar aynıdır, ama maruziyet farklıdır. GraphQL şema deprecation’ı, bir client’ın yalnızca açıkça istediği alanları aldığı bir modeli ele alır, ki bu eklemeli değişiklikleri esasen risksiz ve kaldırmaları tek gerçek tehlike yapar. gRPC client’ları ise sunucunun gönderdiği her şeyi alır ve hepsini kendi derlenmiş şema kopyasına karşı kod çözer; bir client’ın maruziyeti istediğiyle sınırlı değildir, sadece üretilen kodunun ne okuyabildiğiyle sınırlıdır. Bu fark changelog yazarken önemlidir: bir GraphQL kaydı, client’ların istemedikleri alanlardan korunduğunu makul olarak varsayabilir, ve bir gRPC kaydı bunu hiç varsayamaz.
Bir gRPC servisini versiyonlamak REST’in /v1/, /v2/’si gibi mi çalışır?
Niyet aynı olsa bile mekanizma farklıdır. Bir REST API’sinde v1 ve v2 nedir,
versiyonlamayı farklı sözleşmelere hizmet eden paralel URL yolları olarak ele alır; gRPC servisleri
tipik olarak .proto dosyasının kendisindeki paket adı üzerinden versiyonlanır,
payments.v1.InvoiceService, bir client’ın istediği bir URL segmenti yerine çevirdiği tam nitelikli
servis adını değiştirerek payments.v2.InvoiceService olur. Her iki yaklaşım da aynı sorunu çözer,
yeni biri varken eski bir sözleşmenin çalışmaya devam etmesine izin vermek, ama REST geçmişinden
gelen bir ekip genellikle bir versiyon numarasını yanlış yerde arar ve paket bildiriminin o işi
yaptığını kaçırır.
Bir gRPC changelog kaydı gerçekte neyi adlandırmalı?
Mesajı, alan numarasını, ve eylemde bulunup bulunmayacağına karar veren bir okuyucu için önem
sırasına göre, ekleme mi yoksa migrasyon gerektiren bir kaldırma mı olduğunu. “Order’a
shipping_address (alan 8) eklendi”, bir entegratöre üretilen kodu güncellemek ve kullanmaya
başlamak için gereken her şeyi söyler. “Invoice’ta alan 4 rezerve edildi, legacy_customer_id
kayboldu”, ona kod tabanlarındaki herhangi bir şeyin hâlâ o alanı okuyup okumadığını kontrol
etmesini söyler, ki bu REST tarzı bir “yanıttan bir alan kaldırıldı” notunun aynı aciliyetle
iletmediği bir şeydir, çünkü REST kaldırmaları sadece daha az veri döndürürken Protobuf alan
yeniden kullanımı onu aktif olarak bozar.
FAQ
Bir alanın tipi wire formatını bozmadan hiç değiştirilebilir mi?
Sadece Protobuf’ın belgelediği belirli uyumlu gruplar içinde, bazı durumlarda int32’yi
int64’e genişletmek gibi. Protobuf’ın kendi uyumluluk tablosuna karşı kontrol etmediğiniz sürece
herhangi bir tip değişikliğini bozucu olarak ele alın; bir dilin tip sistemiyle benzetme yaparak
uyumluluk varsaymak, bunun yanlış gitme şeklidir.
Protobuf’ta bir alanı deprecate etmek GraphQL’in @deprecated direktifi gibi mi çalışır?
Benzer şekilde: Protobuf, araçların gösterebileceği bir [deprecated = true] alan seçeneğini
destekler. İkisi de zorunlu kılınmaz: bir GraphQL sunucusu deprecate edilmiş bir alan için gelen
sorguyu yine yanıtlar, ve bir protobuf client’ı onu yine kodlar. İkisi de tavsiye niteliğindedir ve
aynı changelog desteğine ihtiyaç duyar.
Her client’ı kontrol ediyorsanız yeniden numaralandırma hiç güvenli midir? Tamamen kapalı bir sistemde, ilke olarak, ama alan numaralarının var olma nedeni olan tüm güvenlik özelliğini ortadan kaldırır, ve “her client’ı kontrol ediyoruz” bir build önbelleğe alındığı, bir deploy geciktirildiği, veya kimsenin hatırlamadığı bir client eklendiği anda doğru olmaktan çıkan bir iddiadır. Numarayı yeniden kullanmak yerine, dahili olarak bile, rezerve edin.
gRPC servisleri genel bir REST API’si gibi bir changelog sayfasına ihtiyaç duyar mı?
Sadece harici ekipler .proto diff’lerini doğrudan okumadan onları tüketiyorsa, dahili API
changelog’ları’nın genel olarak uyguladığı aynı “karşı tarafta
kim var” testi. Sadece aynı ekibin diğer servisleri tarafından tüketilen bir gRPC servisi, genellikle
commit geçmişi lehine resmi bir changelog’u atlayabilir, çünkü onu okuyan herkesin şeması zaten
açıktır.
Bu yazıdaki teknik iddialar bağımsız olarak kontrol edilmedi. Yanlış bir şey varsa bize söyle, düzeltelim.