Tüm eğitimler
TEKNİK REHBER PROTOCOL BUFFERS proto3 2026

Protocol Buffers
şema, wire ve kod üretimi

Schema-first serialization formatını sıfırdan: .proto yazmaktan wire format'ı bayt bayt okumaya, Python ve Go kod üretiminden backward/forward compatibility kurallarına. gRPC'nin altında yatan dil.

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.

JSON
İnsan okunabilir, şema opsiyonel, büyük, parse yavaş. API public yüzeyleri, debug, yapılandırma için ideal.
XML
Çok daha büyük, namespace karmaşık. Eski kurumsal sistemler, SOAP, SVG için. Yeni projede tercih edilmez.
Protobuf
İkili format, şema zorunlu, çok küçük, çok hızlı. Servis-içi iletişim, Kafka şemaları, gRPC, yüksek hacimli pipeline için ideal.
MessagePack
Şema opsiyonel ikili JSON. Protobuf'tan daha az tip güvenli, üretilen kod yok.

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.

proto3 — aynı veri, farklı temsil
// 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.

NE ZAMAN PROTOBUF

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.

user.proto
// 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)
}
syntax = "proto3"
Hangi proto sürümünün kullanıldığını bildirir. Zorunlu; aksi halde derleyici proto2 varsayar. Her zaman dosyanın başına yazın.
package tutorial
Mesajları isim alanı içine koyar. Aynı proje içinde isim çakışmalarını önler. Üretilen dil koduna nasıl yansıdığı bölüm 05'te anlatılmaktadır.
message UserProfile
Veri yapısı tanımı. C'deki struct'a benzer. Büyük harfle başlayan PascalCase kullanımı konvansiyonel.
= 1 / = 2 / = 3
Field number. Binary wire format'ta alan adı DEĞİL bu sayı kullanılır. Bir kez yazıldıktan sonra değiştirilmemelidir — değiştirmek eski verinin okunamamasına yol açar.
FIELD NUMBER KURALLARI

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.

proto3 — stil kuralları
// ✓ 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.

proto3 — tüm skalar tipler
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 / int64
Varint encoding. Pozitif küçük sayılar için verimli. Dikkat: negatif değerler her zaman 10 bayt alır (işaret biti uzar). Negatif bekliyorsanız sint32/sint64 kullanın.
sint32 / sint64
ZigZag encoding: -1 → 1, 1 → 2, -2 → 3, 2 → 4 … Hem pozitif hem negatif değerlerin küçük kalmasını sağlar. Delta değerleri, koordinatlar için idealdir.
uint32 / uint64
İşaretsiz varint. Negatif olmayan tam sayılar için. int32'ye göre artı: 2^31-2^32 arası değerlerde daha az bayt kullanır.
fixed32 / fixed64
Her zaman 4/8 bayt. Değer büyükse (fixed32 için 2^28, fixed64 için 2^56 üstü) varint variantlarından daha verimli. Hash değerleri, IP adresleri için.
float / double
IEEE 754. Wire'da her zaman 4/8 bayt (sabit boyutlu). Fiyat, oran, koordinat gibi ondalıklı değerler için.
string
UTF-8 veya 7-bit ASCII içermek zorunda. Geçersiz UTF-8 içeriyorsa davranış tanımsız. Ham bayt için bytes kullanın.
bytes
Herhangi bir bayt dizisi. Resimler, şifrelenmiş içerik, ikili protokol verileri için. string ile wire kodlaması aynıdır (length-delimited).
int32 ve NEGATİF DEĞERLER

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 — varsayılan değerler
// 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.

user.proto — repeated ve enum eklendi
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
}
repeated string tags = 4
String listesi. Boş liste wire'a yazılmaz. Diller genellikle bunu List<String> / []string / list[str] olarak temsil eder.
repeated int32 scores = 5
Proto3'te sayısal repeated field'lar varsayılan olarak "packed" encoding kullanır: tek bir length-delimited blokta peş peşe değerler. Wire boyutunu küçültür.
UserRole role = 6
Enum field. Varsayılan değer: 0 (USER_ROLE_UNKNOWN). Bilinmeyen bir enum sayısal değer gelirse proto3 onu integer olarak saklar ve yok saymaz.
ENUM'DA SIFIR KURALI

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.

proto3 — enum gelişmiş
// 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.

user.proto — nested, oneof, map
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;
}
nested message
Başka bir mesaj tipini field olarak kullanmak. Wire'da length-delimited olarak encode edilir. Atanmamışsa (null/None), wire'a hiçbir şey yazılmaz.
oneof contact { ... }
İçindeki field'lardan yalnızca biri aynı anda aktif olabilir. Birini set etmek diğerlerini otomatik temizler. Üretilen kodda genellikle union/case/which() yardımcıları gelir.
map<string, int32>
Anahtar-değer haritası. Anahtar: herhangi bir skalar tip (float, double, bytes, enum hariç). Değer: herhangi bir tip (başka bir map hariç). İç temsil: "repeated EntryMessage".
message içinde message
İç içe tanımlanan mesajlar, dış mesajın scope'una sıkışır. Dışarıdan "UserProfile.Preferences" olarak erişilir. İzolasyon için kullanışlı.
MAP VE SIRA

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.

proto3 — oneof kullanımı (Python)
# 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/ dizin yapısı
proto/
├── common/
│   └── types.proto       ← ortak tipler
├── user.proto             ← types.proto'yu import eder
└── order.proto            ← user.proto ve types.proto'yu import eder
common/types.proto
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;
}
order.proto — import kullanımı
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;
}
import "path/to/file.proto"
protoc'un -I ile belirtilen kök dizinine göre relative yol. Örnek: -I./proto ile import "common/types.proto"./proto/common/types.proto okunur.
import public "..."
Transitif import. A, B'yi "import public" ile alırsa, A'yı import eden C dosyaları B'nin mesajlarına da erişebilir. İnce bir araç — çoğu zaman doğrudan import daha anlaşılır.
option go_package
"full/import/path;packagename" formatı. Go modüllerinde gerekli. Yanlış yazılırsa Go importu çalışmaz.
option java_package
Java ve Kotlin için tam paket adı. Üretilen .java dosyaları bu pakete konur.
bash — çoklu dosya projesi derleme
# -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.

user.proto — well-known types
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;
}
Timestamp
seconds (int64) + nanos (int32). Unix epoch bazlı. Dil kütüphaneleri datetime/time.Time'a dönüştürür. JSON: "2026-04-10T14:30:00Z".
Duration
seconds (int64) + nanos (int32). Negatif olabilir. JSON: "3600s" veya "1.5s".
Empty
Boş mesaj. gRPC'de "dönüş değeri yok" veya "parametre yok" için kullanılır. import "google/protobuf/empty.proto"
StringValue / Int32Value / BoolValue / …
Wrapper tipler. Proto3 primitifleri atanmamışsa 0/false/"" döner — null durumu ayırt edilemez. Wrapper ile null set edilebilir. JSON: {"middleName": "Rıfkı"} veya null.
Struct / Value / ListValue
JSON nesnesini protobuf içinde temsil eder. Şeması bilinmeyen dinamik veriler için. JSON round-trip garantili.
Any
type_url (string) + value (bytes). Herhangi bir protobuf mesajını tip bilgisiyle birlikte taşır. Reflection gerektiren sistemlerde kullanılır.
FieldMask
paths ([]string). Partial update pattern için: "hangi field'lar güncellenmeli?". GET paths=["user.name","user.email"] ile yalnızca bu alanların döneceğini belirtir.
Python — Timestamp ve Wrapper kullanımı
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.

bash — kurulum
# 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
bash — Python kodu üretme
# 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
bash — Go kodu üretme
protoc \
  -I./proto \
  --go_out=./gen/go \
  --go_opt=paths=source_relative \
  proto/user.proto
# Çıktı: ./gen/go/user.pb.go
Python — serialize ve deserialize
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
Go — serialize ve deserialize
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
}
PLUGIN MİMARİSİ

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, …).

bash — descriptor set ve decode
# 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:

proto3 — encode edilecek 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:

wire — tag encoding
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)
wire — adım adım decode
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.

wire — varint örnekleri
// 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:

wire — ZigZag encode formülü
// 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)
UNKNOWN FIELDS

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.

proto3 JSON kuralları
// .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
}
int64 / uint64 / sint64
JSON string olarak temsil edilir: "1234567890123". JavaScript Number 53-bit'ten büyük tam sayıları kayıpsız temsil edemez.
bytes
Base64-encoded string: "SGVsbG8gV8O8cmxkIQ==".
bool
true / false (JSON native).
float / double
JSON number. Özel değerler: "Infinity", "-Infinity", "NaN" (string olarak).
enum
String adıyla temsil edilir: "USER_ROLE_ADMIN". Sayısal değer de kabul edilir: 3.
Timestamp
RFC 3339 / ISO 8601: "2026-04-10T14:30:00.123Z". UTC olmalı.
Duration
"Xs" formatı: "3.5s", "86400s".
FieldMask
Virgülle ayrılmış camelCase path'ler: "user.displayName,photo".
Any
{"@type": "type.googleapis.com/tutorial.UserProfile", "name": "Ali", ...}.
Varsayılan değerler
Proto3'te varsayılan değerler JSON'a yazılmaz (0, false, "" atlanır). use_proto3_json_names / emit_default_values ile kontrol edilebilir.
Python — JSON dönüşümü
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)
VARSAYILAN DEĞERLER

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.

proto3 — güvenli değişiklikler
// ✓ 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
}
proto3 — tehlikeli değişiklikler
// ✗ 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:

proto3 — reserved ile koruma
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".
✓ Yeni field ekle
Her zaman yeni, daha önce kullanılmamış bir field number seçin. Eski decoderlar bu field'ı görmezden gelir.
✓ Field'ı optional yap (kaldır)
Kaldırılan field'ı ve adını reserved ile işaretleyin. Eski encoder gönderirse yeni decoder unknown field olarak saklar.
✓ Field adını değiştir
Wire'da sadece number önemli. Ad değişikliği binary uyumluluğunu bozmaz ama JSON uyumluluğunu bozabilir (json_name kullanın).
✗ Field number'ı farklı tipte tekrar kullan
Asla. Eski veriler yanlış yorumlanır, uygulamalar çöker veya sessizce bozuk veri üretir.
✗ repeated'ı optional'a çevir
repeated field gönderilirken, alan taraf optional bekliyor: yalnızca son öğe gelir, diğerleri kaybolur.
✗ enum 0 değerini değiştir
Varsayılan değer 0'dır. 0'ın anlamı değişirse atanmamış field'lar yanlış anlama gelir.

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ı
bash — protoc hızlı başvuru
# 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
proto3 — wire type özeti
// 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.