API değişiklikleri

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şiklikWire’da güvenli miNeden
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ştirmekHayırMevcut her mesaj artık yanlış alan olarak okunur
Yeni numarayla yeni bir alan eklemekEvetEski client’lar tanımadıkları alanları görmezden gelir
Bir alanı kaldırmak, eski numarasını başka bir şey için yeniden kullanmakHayırEski veri yanlış yeni alana kod çözülür
Bir alanın tipini uyumsuz şekilde değiştirmek (örn. int32’den string’e)HayırWire 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.

changeloop'ta ilgili sayfalar: Geliştirici dokümantasyonu, Changelog araçları karşılaştırması

changeloop
Döngüyü kapatan bir changelog geliştiren ekip. Kullanıcıların bir şey ister, ekibin teslim eder, isteyen kişi haberdar olur.