API’mde JSON kullanmayı bırakıp Protobuf’a geçmemin nedeni
(aloisdeniel.com)- Web API’lerinin fiili standardı haline gelen JSON, okunması kolay ve esnek olsa da performans ve kararlılık açısından sınırlara sahip
- Protobuf (Protocol Buffers), katı tip tanımı ve otomatik kod üretimi sayesinde veri yapısını açık biçimde garanti eder
- İkili serileştirme kullanarak JSON’a kıyasla veri boyutunu yaklaşık 3 kat veya daha fazla azaltır ve iletim hızını artırır
- Sunucu ve istemci aynı .proto şemasını paylaştığı için tip uyuşmazlığı veya manuel doğrulama gerekmez
- Hata ayıklama daha zor olsa da performans, bakım kolaylığı ve geliştirme verimliliği açısından Protobuf modern API’ler için daha uygundur
JSON’un yaygınlığı ve sınırları
- JSON, insanın kolay okuyabildiği bir metin formatıdır; veriyi yalnızca basit bir
console.log()ile bile kontrol etmek mümkündür - Web ile kusursuz entegrasyonu sayesinde JavaScript ve backend framework ekosisteminde geniş çapta benimsenmiştir
- Alan ekleme-silme-tip değiştirme konusunda serbestlik sağlayan esneklik sunar, ancak bu da yapı uyuşmazlığına veya hatalara yol açabilir
- Araç ekosistemi zengindir; yalnızca bir metin editörü veya
curlile bile kolayca çalışılabilir - Ancak bu avantajlara rağmen performans ve tip güvenliği açısından daha iyi alternatifler vardır
Protobuf’a genel bakış
- Google tarafından 2001’de geliştirilen ve 2008’de yayımlanan bir ikili serileştirme formatı
- Dahili sistemlerde ve mikroservisler arası iletişimde yaygın olarak kullanılır
- Sıklıkla gRPC ile birlikte kullanılmak zorunda olduğu sanılır, ancak Protobuf bağımsız olarak HTTP API’lerinde de kullanılabilir
- Başlangıçta ikili formatın görünmezliği nedeniyle erişilebilirliği düşük görünse de verimlilik ve kararlılık açısından güçlüdür
Güçlü tip sistemi ve kod üretimi
- Protobuf,
.protodosyaları üzerinden veri yapısını açıkça tanımlar- Her alanın katı bir tipi, sayısal tanımlayıcısı ve sabit bir adı vardır
- Örnek:
message User { int32 id = 1; string name = 2; string email = 3; bool isActive = 4; } protockomutuyla Dart, TypeScript, Kotlin, Swift, C#, Go, Rust gibi birçok dil için otomatik kod üretimi desteklenir- Üretilen kodla serileştirme (
writeToBuffer) ve ters serileştirme (fromBuffer) yapılır; manuel doğrulama veya parse etme gerekmez - Sonuç olarak aynı anda hem zamandan tasarruf hem de bakım kolaylığında artış sağlanır
İkili serileştirmenin verimliliği
- Protobuf, metin yerine ikili veri olarak serileştirildiği için son derece kompakt ve hızlıdır
- Aynı verinin (
Usernesnesi) boyut karşılaştırması:- JSON: 86 bayt (boşluklar kaldırıldığında 68 bayt)
- Protobuf: 30 bayt
- Verimliliğin nedenleri:
- Sayılar için varint encoding kullanılması
- Metin anahtarlar yerine sayısal etiketler kullanılması
- Boşlukların ve gereksiz sözdiziminin kaldırılması
- Opsiyonel alan optimizasyonu
- Sonuç olarak bant genişliği tasarrufu, yanıt hızında artış, mobil veri kullanımında azalma ve kullanıcı deneyiminde iyileşme sağlar
Dart tabanlı Protobuf API örneği
shelfpaketi kullanılarak basit bir HTTP sunucusu kurulupUsernesnesi Protobuf olarak döndürülür- Sunucu kodunun özü:
User()nesnesi oluşturulur vewriteToBuffer()ile serileştirilir- Yanıt başlığında
'content-type': 'application/protobuf'belirtilir
- İstemci,
httppaketi veuser.pb.dartkullanarak Protobuf verisini doğrudan decode eder - Sunucu ve istemci aynı
.protoşemasını paylaştığı için veri yapısı uyuşmazlığı oluşmaz - Aynı yaklaşım Go, Rust, Kotlin, Swift, C#, TypeScript gibi dillerde de aynı şekilde uygulanabilir
JSON’un kalan avantajları
- Protobuf’ta şema olmadan anlamı yorumlamak zordur
- Alan adları yerine yalnızca sayısal tanımlayıcılar göründüğü için insan tarafından okunması zordur
- Karşılaştırma örneği:
- JSON:
{ "id": 42, "name": "Alice" } - Protobuf:
1: 42, 2: "Alice"
- JSON:
- Bu yüzden Protobuf için:
- Özel decode araçları gerekir
- Şema yönetimi ve sürümleme zorunludur
- Buna rağmen performans ve verimlilik avantajları çok daha büyüktür
Sonuç
- Protobuf, olgun ve yüksek performanslı bir serileştirme teknolojisidir ve açık API’lerde de rahatlıkla kullanılabilir
- gRPC olmadan da genel HTTP API içinde bağımsız şekilde çalışır
- Performansı, sağlamlığı, hata azaltımını ve geliştirme verimliliğini birlikte iyileştiren bir araçtır
- Yeni nesil projelerde Protobuf’u benimsemek için fazlasıyla geçerli nedenler vardır
10 yorum
TypeSpec
https://typespec.io/
https://msgpack.org/ buna ne dersiniz?
MessagePack de iyi.
Hata ayıklama için resmi bir decoder’ı bile olmayan bir formatın olgun olduğunu iddia etmenin çelişkili olduğunu düşünüyorum.
Her araçta olduğu gibi bunun da her derde deva bir çözüm olmadığını düşünüyorum ama Protobuf’un da fazlasıyla iyi bir araç olduğunu söyleyebilirim.
Özellikle gömülü ortamda, farklı dillere sahip istemcilere yüksek hacimli ve yüksek frekanslı (saniyede 20 kez) veri göndermem gereken bir durum olmuştu; o zaman nanopb ile bunu oldukça temiz bir şekilde halletmiştim.
Bu kadar katı olunca XML'le gelmez mi zaten? :)
Şema da DTD ile tanımlanıp parser tarafında önbelleğe alınırsa, şemanın yalnızca bir kez gönderilmesi gibi bir etki de olur sanırım.
=> Şema zaten eninde sonunda en az bir kez gönderilmek zorunda değil mi? JSON’da da şema yok değil; sadece verinin içine örtük olarak gömülü durumda, yani aslında şema hiç gönderilmiyor değil. Hatta her bir öğe için şemayı tekrar tekrar gönderdiği için daha da verimsiz. "Şema tabanlı olup aynı zamanda mesajın içinde şemayı da barındıran bir yapı" fikri bence oldukça iyi görünüyor.
Hacker News yorumu
JSON çoğu zaman muğlak ya da garanti edilmeyen veriler göndermeye yol açar. Alan eksikliği, tip hataları, anahtar yazım yanlışları, belgelenmemiş yapılar gibi çeşitli sorunlar ortaya çıkar. Ancak Protobuf, mesaj yapısını
.protodosyalarıyla açıkça tanımlayarak bunun imkansız hale geldiğini savunan bir yazı vardı. Fakat bu, Protobuf’un felsefesini yanlış anlamaktır.proto3, required alanları hiç desteklemez. Resmi belgelerde de (Protobuf Best Practices) “required alanlar zararlı olduğu için kaldırıldı” deniyor. Sonuçta Protobuf istemcileri de JSON API’leri gibi savunmacı yazılmalıdırjson:"-"ile private alanları engellemek önemli. Projemi Gooey üzerinde görebilirsinizSıkıştırılmış JSON gayet kullanılabilir ve ilk iletişim maliyeti düşüktür. Elbette alanlar eksik olursa ya da tipler değişirse sorun çıkar, ama tamamen tiplenmiş yapılar tasarlayıp sürüm senkronizasyon süreçleri kurmaya çalışanların çoğu başarısız olur. Sonunda insan maliyeti daha düşük olan taraf kazanır. Bu yüzden JSON, daha düşük insan iletişim maliyeti sunan bir alternatif çıkana kadar ortadan kaybolmayacak
console.log()ile anında debug edilebilen bir alternatif çıkmadıkça JSON yerini kaybetmeyecekProtobuf kusursuz değil. Sunucu ve istemci farklı zamanlarda dağıtılıp spec sürümleri farklıysa güvenlik bozulur. ID’lerin yeniden kullanılmaması, unknown-field kopyalama gibi yöntemlerle bu hafifletilebilir ama dağıtık sistemler doğası gereği karmaşıktır. Yine de
protobuf3,protobuf2’nin birçok sorununu çözdü. Eskiden varsayılan değerin özellikle mi verildiği yoksa alanın eksik mi olduğu anlaşılamıyordu; artıkmessagetipiyle bu çözülebiliyorYazıda “süper verimli” denmiş ama gzip’ten hiç bahsedilmiyor. Çoğu metin verisi zaten otomatik olarak sıkıştırılarak iletiliyor. Bu nedenle Protobuf, gzip’lenmiş JSON ile karşılaştırılmalı
Daha iyi bir protokolü savunmak güzel ama Protobuf’un hem verimlilikte hem kullanılabilirlikte JSON’un yerini aldığını söylemek zor. Protobuf, katı şeması yüzünden JSON’un iyi olduğu alanları kaçırıyor. Hatta CBOR, JSON’un yerine geçmek için daha uygun olabilir. CBOR, JSON kadar esnek ama daha kompakt bir kodlamaya sahip
1984 tarihli ASN.1, Protobuf’un yaptığını daha esnek şekilde zaten yapıyordu. DER encoding kullanılırsa o kadar da kötü değil. ASN.1 DER örneğine bakabilirsiniz. Protobuf, başardığı şeye kıyasla gereğinden fazla karmaşık
Tüm prodüksiyon sistemimi Protobuf ile kurdum ama yönetmesi başlı başına bir eziyetti. Teknik olarak iyi görünüyor ama pratikte JSON çok daha basit
Protobuf harika ama zero-copy desteklememesi üzücü. Cap’n Proto gibi formatlar serileştirme/ters serileştirme darboğazını ortadan kaldırıyor
Bir NodeJS projesinde tüm API’yi
.protoile tanımlayıp Content-Type’a göre proto ya da JSON dönen bir sunucu yazdım. Swagger’dan çok daha yapısal. Yalnız Google’ın böyle bir özelliği resmi kütüphane olarak sunmaması üzücü. gRPC ise HTTP/2 bağımlılığı nedeniyle kullanışsız. Ayrıca Text proto’nun en iyi statik yapılandırma dili olduğunu düşünüyorumHayalimdeki ikili format, şema tabanlı olup aynı zamanda şemayı mesajın içine gömen bir yapı. Böyle olursa bir vim eklentisiyle doğrudan okunabilir. Milyonlarca nesneyle çalışırken 2GB’lık bir mesaja 1KB’lık şema eklemek büyük yük değil
"Hata ayıklaması zor ama"
Elendi