00 Protocol Buffers nedir, neden
Protobuf, Google'ın 2001'den beri üretimde kullandığı, dil-nötr ve platform-nötr bir serialization formatıdır. Şema önce yazılır; kod daha sonra otomatik üretilir.
Klasik yaklaşımda veriyi JSON veya XML olarak temsil edersiniz: alan adları metne gömülür, her seferinde yeniden parse edilir, şema isteğe bağlıdır. Protobuf'ta alan adları wire'a hiç girmez — yalnızca bir sayı (field number) gider. Şema (.proto dosyası) zorunludur ve kaynak olarak tutulur; her dil için bu şemadan native kod üretilir.
Küçük bir ölçek vermek gerekirse: aynı yapı için JSON ~150 bayt, Protobuf ~35 bayt olabilir. Serileştirme hızı farkı da genellikle 3-10× arasındadır. Ancak bunlar ikincil kazanımlar. Asıl güç: şemanın kontrat olması. Bir field silindiğinde veya tipi değiştiğinde derleme anında hata alırsınız — JSON'da bu sorunlar yalnızca çalışma zamanında (ve çoğu zaman prod'da) ortaya çıkar.
// Protobuf şema (.proto)
message UserProfile {
string name = 1;
int32 age = 2;
string email = 3;
}
// JSON temsili (~66 bayt)
{"name":"Ali","age":30,"email":"ali@ornek.com"}
// Protobuf binary temsili (~20 bayt, hex)
0a 03 41 6c 69 10 1e 1a 0d 61 6c 69 40 6f 72 6e 65 6b 2e 63 6f 6d
Proto2 vs proto3: Proto2 (2008) required ve optional anahtar kelimelerine sahipti; required field'lar şema evrimini neredeyse imkânsız kılıyordu. Proto3 (2016) required'ı kaldırdı, tüm field'ları teknik olarak isteğe bağlı yaptı ve varsayılan değerleri öngörülebilir hale getirdi. Bu rehber tamamen proto3 kapsar.
Servisler arası yüksek hacimli iç trafik, gRPC bağlantıları, event streaming (Kafka, Pub/Sub) ve boyut/hız kritik ikili depolama için seçin. Harici API'ler, tarayıcı doğrudan erişimi veya insan gözden geçirmesi gereken yapılandırmalar için JSON tercih edin.
01 İlk .proto dosyası
Her şey bir metin dosyasıyla başlar. .proto dosyasının anatomisini öğrendikten sonra bu eğitim boyunca aynı UserProfile mesajını adım adım genişleteceğiz.
// Sözdizimi bildirimi — dosyanın ilk boş olmayan satırı olmalı
syntax = "proto3";
// Paket bildirimi — namespace çakışmalarını önler
package tutorial;
// Mesaj tanımı
message UserProfile {
string name = 1; // ad (field number: 1)
int32 age = 2; // yaş (field number: 2)
string email = 3; // e-posta (field number: 3)
}
1–15 arası field number'lar wire'da 1 bayt yer kaplar. 16–2047 arası 2 bayt. Sık kullanılan field'ları 1–15 aralığında tutmak küçük bir verimlilik sağlar. 19000–19999 arası protobuf runtime için rezervedir — kullanmayın.
Proto3'te tüm field'lar varsayılan olarak isteğe bağlıdır. Değer atanmamış bir field, kendi tipinin sıfır değerini döner: sayısal tipler için 0, bool için false, string ve bytes için boş değer, enum için ilk değer (0 olmalı), message tipler için null. Bu davranış proto2'den farklıdır ve şema evrimi açısından önemlidir.
// ✓ Doğru: snake_case field adları
message OrderItem {
string product_id = 1;
int32 quantity = 2;
double unit_price = 3;
bool is_available = 4;
}
// ✓ Doğru: PascalCase mesaj ve enum adları
message ShoppingCart { }
enum OrderStatus { }
// ✗ Yanlış: field number'ı sonradan değiştirme
string name = 5; // eskiden = 1 idi — eski data artık okunmaz
02 Skalar tipler
Proto3'ün 15 skalar tipi, farklı boyut ve varint encoding stratejileri ile gelir. Hangi tiplerin ne zaman kullanılacağını bilmek hem wire verimliliğini hem de tip güvenliğini etkiler.
message ScalarShowcase {
// Tam sayılar — varint encoding (küçük değerler için verimli)
int32 count = 1; // -2^31 .. 2^31-1 (negatif = 10 bayt!)
int64 timestamp_ms = 2; // -2^63 .. 2^63-1 (negatif = 10 bayt!)
uint32 flags = 3; // 0 .. 2^32-1
uint64 user_id = 4; // 0 .. 2^64-1
// ZigZag tam sayılar — negatif değerler için verimli
sint32 delta_x = 5; // -2^31 .. 2^31-1 (negatif küçük kalır)
sint64 delta_y = 6; // -2^63 .. 2^63-1 (negatif küçük kalır)
// Sabit boyutlu tam sayılar — büyük değerler veya sık değişen sayılar için
fixed32 ip_as_int = 7; // her zaman 4 bayt; 2^28 üstü uint32'den iyi
fixed64 hash_value = 8; // her zaman 8 bayt; 2^56 üstü uint64'den iyi
sfixed32 temperature = 9; // her zaman 4 bayt, işaretli
sfixed64 position = 10; // her zaman 8 bayt, işaretli
// Ondalık
float ratio = 11; // 32-bit IEEE 754, her zaman 4 bayt
double price = 12; // 64-bit IEEE 754, her zaman 8 bayt
// Diğer
bool is_active = 13; // true / false, varsayılan: false
string name = 14; // UTF-8 veya 7-bit ASCII zorunlu
bytes thumbnail = 15; // ham bayt dizisi (resim, ikili veri)
}
int32 tipiyle -1 gibi küçük bir negatif sayı depolarsanız wire'da 10 bayt alır — büyük bir varint olarak temsil edilir. Negatif değer bekliyorsanız sint32 kullanın: -1 için yalnızca 0x01 (1 bayt) gönderilir.
// Proto3'te tüm field'ların varsayılan değeri vardır
// Alan yazılmamışsa (wire'da gelmemişse) bu değerler okunur:
// int32/int64/uint32/uint64/sint32/sint64/fixed32/sfixed32/float/double → 0
// bool → false
// string → "" (boş string)
// bytes → [] (boş bayt dizisi)
// enum → ilk tanımlanan değer (0 olmalı)
// message → null / None (dile göre değişir)
// Sorun: "atanmamış" ile "sıfır değer atanmış" ayırt edilemez
// Çözüm: wrapper tipleri (bölüm 06) veya oneof
03 repeated ve enum
repeated ile bir field'ı dizi (liste) haline getirin, enum ile sıralı sabit kümesi tanımlayın. Her ikisi de evrim kurallarını yakından etkiler.
syntax = "proto3";
package tutorial;
enum UserRole {
USER_ROLE_UNKNOWN = 0; // proto3: 0 değeri zorunlu ilk elemandır
USER_ROLE_VIEWER = 1;
USER_ROLE_EDITOR = 2;
USER_ROLE_ADMIN = 3;
}
message UserProfile {
string name = 1;
int32 age = 2;
string email = 3;
repeated string tags = 4; // string dizisi
repeated int32 scores = 5; // int32 dizisi (packed encoding)
UserRole role = 6; // enum field
}
Proto3'te her enum mutlaka 0 değerli bir elemana sahip olmalıdır. Bu eleman "bilinmeyen" veya "atanmamış" durumu temsil etmeli ve genellikle X_UNKNOWN ya da X_UNSPECIFIED adını alır. 0 değeri, field atanmamışsa dönen varsayılan değerdir; anlamlı bir durum için kullanılmamalıdır.
// allow_alias: farklı adlara aynı sayıyı atamak
enum Direction {
option allow_alias = true;
DIRECTION_UNKNOWN = 0;
NORTH = 1;
UP = 1; // NORTH ile alias — allow_alias olmadan hata
SOUTH = 2;
DOWN = 2; // SOUTH ile alias
}
// enum değerleri SCREAMING_SNAKE_CASE kullanır
// enum adı prefix olarak taşınır (çakışma önleme)
enum OrderStatus {
ORDER_STATUS_UNKNOWN = 0;
ORDER_STATUS_PENDING = 1;
ORDER_STATUS_CONFIRMED = 2;
ORDER_STATUS_SHIPPED = 3;
ORDER_STATUS_DELIVERED = 4;
ORDER_STATUS_CANCELLED = 5;
}
04 Nested messages, oneof ve map
Karmaşık veri yapıları için iç içe mesajlar, birden fazla alternatif için oneof, anahtar-değer çiftleri için map kullanılır.
syntax = "proto3";
package tutorial;
message Address {
string street = 1;
string city = 2;
string country = 3;
string zip = 4;
}
message UserProfile {
string name = 1;
int32 age = 2;
Address address = 3; // nested message
// oneof: yalnızca biri atanabilir
oneof contact {
string email = 4;
string phone = 5;
}
// map: string anahtar → int32 değer
map<string, int32> scores = 6;
// mesaj kendi içinde de nested tanımlanabilir
message Preferences {
bool dark_mode = 1;
string language = 2;
int32 items_per_page = 3;
}
Preferences prefs = 7;
}
Map field'larının sırası garanti edilmez — iteration sırası dile ve implementasyona göre değişir. Sıralı tekrara ihtiyaç varsa repeated + nested message tercih edilmeli.
# Python — hangi oneof field'ının set edildiğini kontrol et
user = UserProfile()
user.email = "ali@ornek.com"
print(user.WhichOneof("contact"))
email
user.phone = "+90 555 123 4567" # email otomatik temizlenir
print(user.WhichOneof("contact"))
phone
print(user.email)
← boş, silindi
05 import, package ve çoklu dosya projeleri
Gerçek projelerde .proto dosyaları birbirini import eder. package bildirimi ve dil-özel option'lar üretilen kodun namespace'ini belirler.
proto/
├── common/
│ └── types.proto ← ortak tipler
├── user.proto ← types.proto'yu import eder
└── order.proto ← user.proto ve types.proto'yu import eder
syntax = "proto3";
package tutorial.common;
option go_package = "example.com/myapp/pb/common;common";
option java_package = "com.example.myapp.common";
option python_package = "myapp.pb.common";
message Money {
string currency_code = 1; // "TRY", "USD", "EUR"
int64 amount_micros = 2; // para birimi küçük biriminde (kuruş vb.)
}
message PageInfo {
int32 page_size = 1;
int32 page_number = 2;
int64 total_count = 3;
}
syntax = "proto3";
package tutorial;
// import: protoc'un -I (include path) kök dizinine göre relative
import "common/types.proto";
option go_package = "example.com/myapp/pb;pb";
message OrderLine {
string product_id = 1;
int32 quantity = 2;
tutorial.common.Money price = 3; // tam nitelikli ad
}
message Order {
string order_id = 1;
repeated OrderLine lines = 2;
tutorial.common.Money total = 3;
tutorial.common.PageInfo paging = 4;
}
-I./proto ile import "common/types.proto" → ./proto/common/types.proto okunur.# -I: import root (birden fazla -I kullanılabilir)
# proto3'te well-known types için ikinci -I gerekebilir
protoc \
-I ./proto \
-I $(python3 -c "import grpc_tools; print(grpc_tools.__path__[0])")/... \
--python_out=./gen/python \
--go_out=./gen/go \
--go_opt=paths=source_relative \
proto/common/types.proto \
proto/user.proto \
proto/order.proto
# Üretilen dosyalar:
gen/python/common/types_pb2.py
gen/python/user_pb2.py
gen/python/order_pb2.py
gen/go/common/types.pb.go
gen/go/user.pb.go
gen/go/order.pb.go
06 Well-known types
Google, sık kullanılan kavramlar için standart .proto dosyaları yayımlar. Bu "well-known types" tüm dillerde aynı şekilde davranır ve JSON serializasyonunda özel temsilleri vardır.
syntax = "proto3";
package tutorial;
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/wrappers.proto";
import "google/protobuf/struct.proto";
import "google/protobuf/any.proto";
message UserProfile {
string name = 1;
int32 age = 2;
// Timestamp: Unix epoch'tan saniye + nanosaniye
google.protobuf.Timestamp created_at = 3;
google.protobuf.Timestamp updated_at = 4;
// Duration: saniye + nanosaniye (negatif olabilir)
google.protobuf.Duration session_len = 5;
// Wrapper: primitive'i "nullable" yapar
google.protobuf.StringValue middle_name = 6; // null → ad yok
google.protobuf.BoolValue email_verified = 7;
// Struct: JSON-eşdeğeri dinamik nesne
google.protobuf.Struct metadata = 8;
// Any: type erased — herhangi bir protobuf mesajı içerebilir
google.protobuf.Any extra = 9;
}
import "google/protobuf/empty.proto"from google.protobuf import timestamp_pb2, wrappers_pb2
from datetime import datetime, timezone
import tutorial_pb2
user = tutorial_pb2.UserProfile()
user.name = "Ali Yılmaz"
# Timestamp: datetime → protobuf
now = datetime.now(timezone.utc)
user.created_at.FromDatetime(now)
# Wrapper: nullable string
user.middle_name.CopyFrom(wrappers_pb2.StringValue(value="Rıfkı"))
# Wrapper null kontrolü
if user.HasField("middle_name"):
print(f"Orta ad: {user.middle_name.value}")
else:
print("Orta ad yok")
07 protoc ve kod üretimi
protoc, .proto dosyasını parse eden derleyicidir. Dil-özel kod üretimi ise protoc-gen-XXX plugin'leri aracılığıyla yapılır. Aynı .proto'dan Python ve Go kodu üretelim.
# Ubuntu/Debian
sudo apt install -y protobuf-compiler
protoc --version
libprotoc 3.21.12
# Python: protobuf runtime + grpcio-tools (protoc-gen-python içerir)
pip install protobuf grpcio-tools
# Go: protoc-gen-go plugin
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
# Çıktı: $GOPATH/bin/protoc-gen-go
# $GOPATH/bin PATH'de olmalı — protoc bunu bulacak
# Basit tek dosya
protoc -I. --python_out=./gen user.proto
# Çıktı: ./gen/user_pb2.py
# grpcio-tools ile (well-known types dahil)
python3 -m grpc_tools.protoc \
-I./proto \
--python_out=./gen/python \
proto/user.proto
protoc \
-I./proto \
--go_out=./gen/go \
--go_opt=paths=source_relative \
proto/user.proto
# Çıktı: ./gen/go/user.pb.go
import user_pb2
# Mesaj oluştur ve doldur
user = user_pb2.UserProfile()
user.name = "Ali"
user.age = 30
user.email = "ali@ornek.com"
# Serileştir → bytes
data = user.SerializeToString()
print(data.hex())
0a03416c6910 1e 1a0d616c69406f726e656b2e636f6d
# Dosyaya yaz
with open("user.bin", "wb") as f:
f.write(data)
# Dosyadan oku ve parse et
user2 = user_pb2.UserProfile()
with open("user.bin", "rb") as f:
user2.ParseFromString(f.read())
print(user2.name, user2.age, user2.email)
Ali 30 ali@ornek.com
package main
import (
"fmt"
"os"
"google.golang.org/protobuf/proto"
pb "example.com/myapp/gen/go"
)
func main() {
// Mesaj oluştur
user := &pb.UserProfile{
Name: "Ali",
Age: 30,
Email: "ali@ornek.com",
}
// Serileştir → []byte
data, err := proto.Marshal(user)
if err != nil { panic(err) }
fmt.Printf("%x\n", data)
// 0a03416c6910 1e 1a0d616c69406f726e656b2e636f6d
// Dosyaya yaz
os.WriteFile("user.bin", data, 0644)
// Dosyadan oku ve parse et
raw, _ := os.ReadFile("user.bin")
user2 := &pb.UserProfile{}
proto.Unmarshal(raw, user2)
fmt.Println(user2.Name, user2.Age, user2.Email)
// Ali 30 ali@ornek.com
}
protoc, .proto dosyasını parse eder ve bir FileDescriptorSet'e dönüştürür. Ardından --XX_out flag'leri için protoc-gen-XX binary'sini PATH'de arar ve bunu çağırır. Bu sayede topluluk tarafından yazılmış onlarca dil için plugin mevcuttur (Rust, TypeScript, C#, Dart, Swift, …).
# Descriptor set: şemanın binary gösterimi (reflection için)
protoc \
-I./proto \
--descriptor_set_out=desc.pb \
--include_imports \
proto/user.proto
# Binary veriyi insan-okunur metin formatında decode et
protoc --decode=tutorial.UserProfile desc.pb < user.bin
name: "Ali"
age: 30
email: "ali@ornek.com"
# Şema bilinmiyorsa raw decode (field numaraları görünür)
protoc --decode_raw < user.bin
1: "Ali"
2: 30
3: "ali@ornek.com"
08 Wire format: bayt bayt çözmek
Protobuf'ı gerçekten anlamak için bir mesajın nasıl encode edildiğini bilmek gerekir. Şimdi tek tek baytlara bakıyoruz.
İnceleyeceğimiz mesaj:
message UserProfile {
string name = 1;
int32 age = 2;
string email = 3;
}
// Değer: name="Ali", age=30, email="ali@ornek.com"
// Binary (hex): 0a 03 41 6c 69 10 1e 1a 0d 61 6c 69 40 6f 72 6e 65 6b 2e 63 6f 6d
Tag formülü: Her field, bir tag baytıyla başlar:
tag = (field_number << 3) | wire_type
// Wire type tablosu:
// 0 → Varint (int32, int64, uint32, uint64, sint32, sint64, bool, enum)
// 1 → 64-bit (fixed64, sfixed64, double)
// 2 → Length-delim. (string, bytes, message, repeated packed)
// 5 → 32-bit (fixed32, sfixed32, float)
0a 03 41 6c 69 10 1e 1a 0d 61 6c 69 40 6f 72 6e 65 6b 2e 63 6f 6d
─── Field 1: name = "Ali" ───────────────────────────────────────────────
0a ← tag: 0x0a = 0b0000_1010 → field_number = 0a >> 3 = 1, wire_type = 0a & 7 = 2
wire_type 2 = length-delimited (string)
03 ← uzunluk: 3 bayt gelecek
41 ← 'A' (ASCII 65)
6c ← 'l' (ASCII 108)
69 ← 'i' (ASCII 105)
→ name = "Ali" ✓
─── Field 2: age = 30 ───────────────────────────────────────────────────
10 ← tag: 0x10 = 0b0001_0000 → field_number = 10 >> 3 = 2, wire_type = 10 & 7 = 0
wire_type 0 = varint
1e ← varint: MSB=0 (tek bayt), değer = 0x1e = 30
→ age = 30 ✓
─── Field 3: email = "ali@ornek.com" ────────────────────────────────────
1a ← tag: 0x1a = 0b0001_1010 → field_number = 1a >> 3 = 3, wire_type = 1a & 7 = 2
wire_type 2 = length-delimited (string)
0d ← uzunluk: 13 bayt gelecek
61 6c 69 40 6f 72 6e 65 6b 2e 63 6f 6d
← 'a' 'l' 'i' '@' 'o' 'r' 'n' 'e' 'k' '.' 'c' 'o' 'm'
→ email = "ali@ornek.com" ✓
Varint encoding
Varint, değeri 7'şer bitlik gruplara böler. Her baytın MSB (en anlamlı biti) "devam baytı var mı?" bayrağıdır; 1 ise bir sonraki bayt da aynı sayıya ait, 0 ise son bayt.
// 1 → 0x01 (1 bayt, MSB=0: son bayt)
01
// 127 → 0x7f (1 bayt)
7f
// 128 → 0x80 0x01 (2 bayt)
// 128 = 0b1000_0000
// Alt 7 bit: 0b000_0000 + MSB devam bayrağı = 0x80
// Üst 7 bit: 0b000_0001 + MSB son bayrağı = 0x01
80 01
// 300 → 0xac 0x02
// 300 = 0b1_0010_1100
// Alt 7 bit: 010_1100 + devam = 0b1_010_1100 = 0xac
// Üst 7 bit: 000_0010 + son = 0b0_000_0010 = 0x02
ac 02
ZigZag encoding (sint32/sint64)
Normal varint'te -1 encode edildiğinde imzalı sayı uzar ve 10 bayta ulaşır. ZigZag, negatif sayıları küçük pozitif sayılara çevirir:
// encode: (n << 1) ^ (n >> 31) [sint32 için]
// decode: (n >>> 1) ^ -(n & 1)
// 0 → 0 (0x00, 1 bayt)
// -1 → 1 (0x01, 1 bayt) ← int32 ile 10 bayt olurdu
// 1 → 2 (0x02, 1 bayt)
// -2 → 3 (0x03, 1 bayt)
// 2 → 4 (0x04, 1 bayt)
// -64 → 127 (0x7f, 1 bayt)
// 64 → 128 (0x80 0x01, 2 bayt)
// 2147483647 → 4294967294 (0xfe 0xff 0xff 0xff 0x0f, 5 bayt)
// -2147483648 → 4294967295 (0xff 0xff 0xff 0xff 0x0f, 5 bayt)
Decoder'ın tanımadığı field_number'ları sessizce atlanır. Yeni bir field eklendiğinde eski decoder'lar bu veriyi görmezden gelir ama kaybetmez (çoğu implementasyonda saklanır). Bu, forward compatibility'nin temelidir — yeni gönderen, eski alan kodunu bozmaz.
09 JSON mapping
Proto3 mesajları canonical JSON formatına dönüştürülebilir. Bu özellik, iç binary iletişimi harici API'lere açmayı ya da debug yapmayı kolaylaştırır.
// .proto field adı: snake_case → JSON: camelCase (otomatik)
// user_id → "userId"
// created_at → "createdAt"
// is_active → "isActive"
// json_name ile özelleştirme:
message Example {
string first_name = 1 [json_name = "firstName"]; // varsayılan zaten bu
string xml_id = 2 [json_name = "xmlID"]; // özel isim
}
from google.protobuf import json_format
import user_pb2
user = user_pb2.UserProfile(name="Ali", age=30, email="ali@ornek.com")
# Proto → JSON string
json_str = json_format.MessageToJson(user)
print(json_str)
{
"name": "Ali",
"age": 30,
"email": "ali@ornek.com"
}
# JSON string → Proto
user2 = json_format.Parse(json_str, user_pb2.UserProfile())
print(user2.name)
Ali
# Python dict → Proto
d = {"name": "Büşra", "age": 25}
user3 = json_format.ParseDict(d, user_pb2.UserProfile())
# snake_case ile JSON (varsayılan camelCase yerine)
json_str2 = json_format.MessageToJson(
user, preserving_proto_field_name=True
)
# {"name": "Ali", "age": 30, "email": "ali@ornek.com"}
# (bu durumda fark yok ama çok kelimeli field'larda olur)
Proto3 JSON'da varsayılan değer (0, false, "") olan field'lar çıktıya dahil edilmez. Alıcının "0 mı yoksa belirtilmemiş mi?" ayrımı yapabilmesi gerekiyorsa wrapper tiplerini (Int32Value, BoolValue…) veya emit_defaults=True seçeneğini kullanın.
10 Evolution: backward ve forward compatibility
Protobuf'ın gerçek gücü burada: şema değiştirirken eski ve yeni encoder/decoder'ların birbiriyle konuşmaya devam etmesi. Bu garantiyi korumak için kurallara uymak şarttır.
// ✓ Yeni field ekleme — HER ZAMAN yeni field number kullanın
message UserProfile {
string name = 1;
int32 age = 2;
string email = 3;
string phone = 4; ← yeni field eklendi; eski decoder atlar
}
// ✓ Field adını değiştirme — wire'da sadece number var
message UserProfile {
string full_name = 1; ← "name" → "full_name"; number değişmedi → uyumlu
int32 age = 2;
}
// ✓ Uyumlu tip dönüşümleri (wire type aynı kaldığında)
// int32 ↔ int64 ↔ uint32 ↔ uint64 ↔ bool (hepsi varint)
// string ↔ bytes (hepsi length-delimited)
// fixed32 ↔ sfixed32 ↔ float (hepsi 4-byte)
// fixed64 ↔ sfixed64 ↔ double (hepsi 8-byte)
// ✓ Enum değeri ekleme
enum OrderStatus {
ORDER_STATUS_UNKNOWN = 0;
ORDER_STATUS_PENDING = 1;
ORDER_STATUS_CONFIRMED = 2;
ORDER_STATUS_REFUNDED = 6; ← yeni değer; eski decoder: 0 (UNKNOWN) döner
}
// ✗ Field number tekrar kullanma — VERİ BOZULUR
message UserProfile {
// string name = 1; ← silindi
int32 score = 1; ← ESKİ NUMBER! "Ali" string'i int32 olarak parse edilir
}
// ✗ Wire type değiştirme (string → int32 veya benzer)
message Example {
int32 user_id = 1; // eskiden: string user_id = 1;
// Alıcı string bekliyor, gönderen int32 gönderiyor → parse hatası
}
// ✗ Enum 0 değerini kaldırmak veya değiştirmek
enum Status {
// STATUS_UNKNOWN = 0; ← kaldırıldı
STATUS_ACTIVE = 0; ← 0 artık farklı anlama geliyor; eski decoderlar yanılır
}
// ✗ repeated → optional (veya tersi)
// repeated field: wire'da aynı tag birden fazla geliyor
// optional field: yalnızca son değer alınır, oncekiler kaybolur
Bir field'ı sildiğinizde, o field number'ı ve adını kalıcı olarak rezerve edin. Bu, ileride yanlışlıkla aynı number'ın farklı bir field için kullanılmasını derleme zamanında engeller:
message UserProfile {
// Silinen field'ları rezerve et
reserved 4, 5, 6; // bu field number'lar artık kullanılamaz
reserved "phone", "fax"; // bu adlar artık kullanılamaz
string name = 1;
int32 age = 2;
string email = 3;
// 4 = phone (silindi, reserved ile korunuyor)
// 5 = fax (silindi, reserved ile korunuyor)
string bio = 7; // yeni field — 6 değil 7 seçildi (güvenli)
}
// reserved eklemeden sonra "phone" field'ı eklemeye çalışırsak:
// protoc: Field name "phone" is reserved in message "UserProfile".
11 Özet ve köprüler
Hatırlanacaklar
- Field number wire'da alan adının yerine geçer — değiştirme, sil ama reserved ile işaretle
- 1–15 arası field number'lar 1 bayt, 16–2047 arası 2 bayt tag kullanır
- Negatif değer içeren int32 → sint32 kullan (ZigZag: -1 = 1 bayt vs 10 bayt)
- Proto3'te tüm field'ların varsayılan değeri var: 0 / false / "" / ilk enum / null
- repeated sayısal field'lar proto3'te packed encoding kullanır (tek blok, daha küçük)
- oneof: birini set etmek diğerlerini temizler; WhichOneof() ile aktif olanı öğren
- map<K,V> key: float/double/bytes/enum olamaz; string ve integral tipler olabilir
- Well-known types: Timestamp/Duration/Empty/Any/Struct/FieldMask/Wrappers
- Wrapper tipler (StringValue vb.) proto3'te null ve "boş string" ayrımı için gerekir
- Tag = (field_number << 3) | wire_type; wire_type: 0 varint, 2 length-delimited
- JSON mapping: snake_case → camelCase otomatik; int64 → string; bytes → base64
- Evolution güvenli: yeni field ekle, adı değiştir, int32↔int64 geçiş yapabilirsin
- Evolution tehlikeli: field number tekrar kullan, wire type değiştir, enum 0'ı kaldır
- Silinen field → reserved hem number hem name — derleme zamanı koruması
# Python kodu üret
protoc -I./proto --python_out=./gen proto/user.proto
# Go kodu üret
protoc -I./proto --go_out=./gen --go_opt=paths=source_relative proto/user.proto
# Descriptor set (reflection için)
protoc -I./proto --descriptor_set_out=desc.pb --include_imports proto/user.proto
# Binary → insan-okunur decode
protoc --decode=tutorial.UserProfile desc.pb < user.bin
# Şema bilinmeden decode
protoc --decode_raw < user.bin
// Wire type 0 (varint):
// int32, int64, uint32, uint64, sint32, sint64, bool, enum
// Wire type 1 (64-bit sabit):
// fixed64, sfixed64, double
// Wire type 2 (length-delimited):
// string, bytes, embedded message, packed repeated
// Wire type 5 (32-bit sabit):
// fixed32, sfixed32, float
// Tag hesaplama: (field_number << 3) | wire_type
// field 1, string → (1 << 3) | 2 = 0x0a
// field 2, int32 → (2 << 3) | 0 = 0x10
// field 3, string → (3 << 3) | 2 = 0x1a
// field 4, bool → (4 << 3) | 0 = 0x20
// field 15, string → (15 << 3) | 2 = 0x7a (1 bayt — son 1 baytlık tag)
// field 16, string → (16 << 3) | 2 = 0x82 0x01 (2 bayt)
Sonraki adım: gRPC eğitimi — artık protobuf'ı derinlemesine biliyorsunuz; service, rpc, stream kavramları ve HTTP/2 üzerinde protobuf aktarımı oradadır.