Protobuf protokolü ile REST API ve Mikroservice’lerinizi güçlendirin!
Merhabalar.
Bu yazımda Google’ın geliştirdiği ve kendi içinde de kullandığı JSON ve XML e göre daha performanslı ve verimli Protocol Buffer (Protobuf) protokolünden bahsedeceğim.
Yazı İçeriği
- Hikaye
- Protocol Buffer’ a Giriş
2.1. İlk Mesajı Tanımlama
2.1.1. Mesaj Tipleri
2.1.2. Tag (Etkiket) Kavramı
2.1.3. Repeated (Tekrarlanan) Alanlar
2.1.4. Default (Varsayılan) Değerler
2.1.5. Enum
2.1.6. Reserved Keywords (Ayrılan Keywordlar)
2.1.7. Maps Koleksiyonu
2.1.8. Oneof Keywordü
2.1.9. İç İçe (Nested) Tipler
2.1.10. TimeStamp
2.1.11. Duration
2.2. Örnek Uygulama
1. Hikaye
JSON (JavaScript Nesne Gösterimi), REST API'ler da uzun zamandır veri iletişim standartı olmuştur. Uzun zaman önce geliştiriciler, JSON protokolünü XML e tercih ettiler. Çünkü JSON kompakt, şemasız, insan tarafından okunabilir ve hat üzerinden aktarılması kolaydı.
JSON’ın şema içermeyen yapısı, alan ekleyebilmenizi veya kaldırabilmenizi sağlar. Ancak JSON verilerinde eklenen veya çıkartılan alanlar nedeniyle client veya server uygulamarınız için sııkıntı olmaya başlayacaktır. Kullanılan mikroservislerinizin sayısı arttıkça servisler arası iletişimi sağlamak için JSON da büyür.
Ayrıca, JSON alan adlarını (Dizileri kullanıyorsanız) yineleyerek gereksiz yere daha fazla yer kaplar ve veri yapınızı iç içe yerleştirmeye başladığınızda insan tarafından okunamayan hale gelir.
2001 yılında Google, JSON’un tüm eksikliklerini gidermek için Protobuf (Protokol Arabelleklerinin kısaltması) adlı bir kurum içi, platform ve dilden bağımsız, veri serileştirme biçimi geliştirdi. Protobuf’un tasarım hedefleri basitlik ve hızdı.
2. Protocol Buffer’ a Giriş
Protocol Buffer ı diğerlerinden (JSON ve XML) ayıran özellik veriyi text yerine binary serialization yapıyor olmasıdır. Bunun sayesinde diğerlerine göre hatırı sayılır miktarda daha az yer kaplıyor olmasıdır. Aynı zamanda string parsing olmadığından dolayı memory açısından da etkilidir.
JSON veya XML gibi human-readable olmadığı veri okunabilirliğinin önemli olmadığı yerlerde, cpu, memory ve bandwith kullanımının önemli olduğu uygulamarlarda tercih edilmelidir.
Aşağıda performans sonuçları verilmiştir.
2.1. İlk Mesajı tanımlama
2.1.1. Mesaj Tipleri
- Number: double, float, int32, int64, unit32 ….. alan türü olarak kullanılır
- Boolean: alan türünde boolean türünde bir alan istediğinizde ‘bool’ kullanılır
- String: Bir dize her zaman UTF-8 kodlu veya 7 bit ASCII metin
- Bytes: Herhangi bir bayt dizisi dizisi için ‘bayt’ kullanılır.
2.1.2. Tag (Etkiket) Kavramı
Etiket en önemli unsurdur. En küçüğü 1, en büyüğü (2^19)–1'dir. Ayrıldıkları için 19000'den 19999'a kadar olan sayıları kullanamayız.
2.1.3. Repeated (Tekrarlanan) Alanlar
Tekrarlanan alanlar bir ‘liste’ veya ‘dizi’ yapmak için kullanılır.
repeated string numbers;
2.1.4. Default (Varsayılan) Değerler
Tüm alanlar belirtilmediğinde varsayılan bir değer alır.
- bool: false
- number (int32): 0
- string: empty string
- bytes: empty bytes
- enum: first value
- repeated: empty list
2.1.5. Enum
Bir alanın önceden alabileceği tüm değerleri bildiğinizde enum’u kullanabilirsiniz. Numaralandırmanın ilk değeri varsayılan değerdir. 0 etiketi ile başlamalıdır.
enum Eyecolor {
UNKNOWN_EYE_COLOR = 0;
EYE_GREEN = 1;
EYE_BROWN = 2;
};
2.1.6. Reserved Keywords (Ayrılan Keywordlar)
Bir alanı kaldırırken, her zaman etiketi ve adı reserve etmelisiniz. Bu, etiketin yeniden kullanılmasını önler. Kod tabanındaki çakışmaların önlenmesi için bu gereklidir.
message Student{
reserved 2, 5 , 10 to 12;
reserved "name" , "mail";
}
2.1.7. Maps Koleksiyonu
Mapsler, diğer programlama dillerinde olduğu gibi anahtar değer çiftlerini saklamak için kullanılır. Mapler tekrarlanamaz. Aşağıda bir map örneği yer almaktadır.
message Student{
int32 student_id = 1;
map<string,string> additional_info;
}
2.1.8. Oneof Keywordü
Çok sayıda isteğe bağlı alan içeren ve aynı anda en fazla bir alanın ayarlanacağı bir mesajınız varsa, oneof özelliğini kullanarak bu davranışı uygulayabilir ve hafızadan tasarruf edebilirsiniz. Oneof alanları, oneof paylaşım belleğindeki tüm alanlar dışında isteğe bağlı alanlara benzer ve en fazla bir alan aynı anda ayarlanabilir. Oneof öğesinin herhangi bir üyesinin ayarlanması diğer tüm üyeleri otomatik olarak siler.
message Student{
int32 student_id = 1;
oneof language_course{
string lang_eng = 2;
string lang_fre = 3;
}
}
2.1.9. İç İçe (Nested) Tipler
Aşağıdaki örnekte olduğu gibi diğer ileti türlerinin içindeki ileti türlerini tanımlayabilir ve kullanabilirsiniz.
message SearchResponse {
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}
repeated Result result = 1;
}
2.1.10. TimeStamp
TimeStamp protobuf da default olarak tanımlı değildir. Ancak “google/protobuf/timestamp.proto
” proto dosyasına dahil ederek çağırabilirisiniz. Alanları saniye ve nanosaniyedir.
import "google/protobuf/timestamp.proto";
message Student{
google.protobuf.Timestamp last_updated = 1;;
}
2.1.11. Duration
Duration parametreside timestamp deki gibi import edilerek kullanılabilir. İki zaman damgası arasındaki zaman aralığını temsil eder. Zaman damgası gibi saniyeler ve nanosaniye içerir.
import "google/protobuf/duration.proto";
message Student{
google.protobuf.Duration validity = 1;
}
2.2. Örnek Uygulama
Protobuffer’ın kolay kullanımı için bir çok dilde third-party toolları vardır. Biz örnek uygulamayı .NET Core da yapacağımız için aşağıdaki Nuget paketi projenize indirmeniz gerekir.
Install-Package protobuf-net
Kütüphaneye ait detaylı kullanıma aşağıdaki linkten ulaşabilirsiniz.
Öncelikle serialize edeceğimiz modelimizi oluşturalım.
[ProtoContract]
class Person {
[ProtoMember(1)]
public int Id {get;set;}
[ProtoMember(2)]
public string Name {get;set;}
[ProtoMember(3)]
public Address Address {get;set;}
}
[ProtoContract]
class Address {
[ProtoMember(1)]
public string Line1 {get;set;}
[ProtoMember(2)]
public string Line2 {get;set;}
}
[ProtoContract] : Modellerin bu attribute ile işaretlenmiş olması gerekmektedir. Anlamı, bu modelin protobuf ile Serializa veya Deserialize olacağı anlamına gelmektedir.
[ProtoMember(1)] : Protobuf ile transfer edilecek her bir property, bu attribute ile o sınıfa ait unique bir sayı ile işaretlenmelidir.
Dosyaya Serialize İşlemi
var person = new Person {
Id = 12345, Name = "Fred",
Address = new Address {
Line1 = "Flat 1",
Line2 = "The Meadows"
}
};
using (var file = File.Create("person.bin")) {
Serializer.Serialize(file, person);
}
Dosyadan Deserialize İşlemi
Person newPerson;
using (var file = File.OpenRead("person.bin")) {
newPerson = Serializer.Deserialize<Person>(file);
}
Serialize/Deserialize İşlemleri için Extension Methodlar
public static byte[] ProtoSerialize<T>(T record) where T : class
{
if (null == record) return null;
try
{
using (var stream = new MemoryStream())
{
Serializer.Serialize(stream, record);
return stream.ToArray();
}
}
catch
{
// Log error
throw;
}
}public static T ProtoDeserialize<T>(byte[] data) where T : class
{
if (null == data) return null;
try
{
using (var stream = new MemoryStream(data))
{
return Serializer.Deserialize<T>(stream);
}
}
catch
{
// Log error
throw;
}
}