gRPC API Nedir: REST’e Alternatif Yüksek Performanslı İletişim Rehberi

Microservice mimarisine geçtiğinizde ya da yüksek trafikli sistemler kurmaya başladığınızda, REST API’lerinin bir noktada yetersiz kaldığını fark edersiniz. Geç gelen yanıtlar, şişirilmiş JSON payload’ları, tip güvenliğinin yokluğu… İşte tam burada gRPC devreye giriyor. Google tarafından geliştirilen ve 2016’da açık kaynak olarak yayınlanan gRPC, özellikle servisler arası iletişimde REST’e göre ciddi avantajlar sunuyor. Bu rehberde gRPC’yi sıfırdan anlayacak, pratik örneklerle nasıl kurulup kullanıldığını görecek ve gerçek dünya senaryolarında REST ile karşılaştırmalı olarak değerlendireceğiz.

gRPC Nedir ve Neden Önemlidir

gRPC, Google Remote Procedure Call kelimelerinin kısaltmasıdır. Temelde bir servisin başka bir servisteki metodu sanki lokal bir fonksiyonmuş gibi çağırmasını sağlar. Bu RPC (Remote Procedure Call) konsepti aslında yeni değil, ama gRPC bunu modern altyapıyla yeniden tanımladı.

gRPC’nin altında iki temel teknoloji yatıyor:

  • HTTP/2: Multiplexing, header compression ve binary framing ile HTTP/1.1’e göre çok daha verimli
  • Protocol Buffers (protobuf): Google’ın geliştirdiği, JSON’a kıyasla 5-10 kat daha küçük ve hızlı binary serialization formatı

REST API’lerde her şeyi kendiniz tanımlarsınız: URL yapısı, HTTP metodları, request/response formatı, hata kodları. gRPC’de ise önce bir .proto dosyası yazarsınız, bu dosya hem servis tanımını hem de veri yapılarını içerir. Sonra bu dosyadan otomatik olarak client ve server kodu üretilir. Bu sayede tip güvenliği garantidir ve API kontratı merkezi bir yerde yaşar.

Protocol Buffers (Protobuf) Anlamak

Protobuf, gRPC’nin kalbidir. Önce bunu anlamadan gRPC’yi tam kavrayamazsınız.

JSON ile bir kullanıcı verisi şöyle gönderirsiniz:

# JSON boyutu örneği - tipik bir kullanıcı objesi
echo '{"id":1,"name":"Ahmet Yilmaz","email":"[email protected]","age":30}' | wc -c
# Çıktı: 65 byte

Aynı veriyi protobuf ile tanımlayıp serialize ettiğinizde bu boyut 15-20 byte’a kadar düşebilir. Ağ üzerinden milyonlarca istek geçtiğinde bu fark dramatik hale gelir.

Bir .proto dosyası şöyle görünür:

# user.proto dosyasını oluşturalım
cat > user.proto << 'EOF'
syntax = "proto3";

package user;

option go_package = "./proto/user";

// Kullanıcı veri modeli
message User {
  int32 id = 1;
  string name = 2;
  string email = 3;
  int32 age = 4;
  repeated string roles = 5;
}

// İstek ve yanıt mesajları
message GetUserRequest {
  int32 id = 1;
}

message CreateUserRequest {
  string name = 1;
  string email = 2;
  int32 age = 3;
}

message UserResponse {
  bool success = 1;
  string message = 2;
  User user = 3;
}

// Servis tanımı
service UserService {
  rpc GetUser(GetUserRequest) returns (UserResponse);
  rpc CreateUser(CreateUserRequest) returns (UserResponse);
  rpc ListUsers(ListUsersRequest) returns (stream UserResponse);
}

message ListUsersRequest {
  int32 page = 1;
  int32 limit = 2;
}
EOF

echo "Proto dosyası oluşturuldu"

Burada dikkat edilmesi gereken nokta: her field’ın bir numarası var (1, 2, 3…). Bu numaralar binary encoding’de field’ları tanımlamak için kullanılır. Protobuf, field adlarını değil bu numaraları kullanır, bu yüzden boyut bu kadar küçük olur.

Go ile gRPC Server Kurulumu

Şimdi pratik bir örnek yapalım. Go, gRPC için en popüler dillerden biri. Önce gerekli araçları kuralım:

# Go kurulu değilse önce onu kurun
# Ubuntu/Debian için:
sudo apt update && sudo apt install -y golang-go

# Protobuf compiler kurulumu
sudo apt install -y protobuf-compiler

# Go için protobuf ve gRPC plugin'leri
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# PATH'e ekleyin
export PATH="$PATH:$(go env GOPATH)/bin"

# Kurulumu doğrulayın
protoc --version
protoc-gen-go --version

Projeyi oluşturalım:

# Proje dizini oluştur
mkdir grpc-user-service && cd grpc-user-service
go mod init github.com/sysadmin/grpc-user-service

# Bağımlılıkları ekle
go get google.golang.org/grpc
go get google.golang.org/protobuf

# Dizin yapısını oluştur
mkdir -p proto server client

# Proto dosyasını taşı
mv ../user.proto proto/

# Kodu üret
protoc --go_out=. --go_opt=paths=source_relative 
       --go-grpc_out=. --go-grpc_opt=paths=source_relative 
       proto/user.proto

ls proto/
# user.pb.go ve user_grpc.pb.go dosyaları oluşmuş olmalı

Şimdi server implementasyonunu yazalım:

# Server kodunu oluştur
cat > server/main.go << 'EOF'
package main

import (
    "context"
    "log"
    "net"

    pb "github.com/sysadmin/grpc-user-service/proto"
    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
)

// UserServer struct - servis implementasyonu
type UserServer struct {
    pb.UnimplementedUserServiceServer
    users map[int32]*pb.User
}

// GetUser - tek kullanıcı getir
func (s *UserServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.UserResponse, error) {
    log.Printf("GetUser çağrıldı, ID: %d", req.Id)

    user, exists := s.users[req.Id]
    if !exists {
        return &pb.UserResponse{
            Success: false,
            Message: "Kullanıcı bulunamadı",
        }, nil
    }

    return &pb.UserResponse{
        Success: true,
        Message: "Kullanıcı bulundu",
        User:    user,
    }, nil
}

// CreateUser - yeni kullanıcı oluştur
func (s *UserServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.UserResponse, error) {
    newID := int32(len(s.users) + 1)
    user := &pb.User{
        Id:    newID,
        Name:  req.Name,
        Email: req.Email,
        Age:   req.Age,
    }

    s.users[newID] = user
    log.Printf("Yeni kullanıcı oluşturuldu: %s (ID: %d)", user.Name, user.Id)

    return &pb.UserResponse{
        Success: true,
        Message: "Kullanıcı oluşturuldu",
        User:    user,
    }, nil
}

func main() {
    // TCP listener oluştur
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("Dinleme başarısız: %v", err)
    }

    // gRPC server oluştur
    grpcServer := grpc.NewServer()

    // Servisi kaydet
    userServer := &UserServer{
        users: make(map[int32]*pb.User),
    }
    pb.RegisterUserServiceServer(grpcServer, userServer)

    // Reflection ekle (grpcurl gibi araçlar için gerekli)
    reflection.Register(grpcServer)

    log.Println("gRPC server :50051 portunda başlatıldı")
    if err := grpcServer.Serve(lis); err != nil {
        log.Fatalf("Server hatası: %v", err)
    }
}
EOF

# Server'ı başlat
go run server/main.go &
echo "Server PID: $!"

gRPC Client Yazımı ve Test

Server hazır, şimdi client tarafına geçelim:

# Client kodunu oluştur
cat > client/main.go << 'EOF'
package main

import (
    "context"
    "log"
    "time"

    pb "github.com/sysadmin/grpc-user-service/proto"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
)

func main() {
    // Server'a bağlan (production'da TLS kullanın!)
    conn, err := grpc.Dial(
        "localhost:50051",
        grpc.WithTransportCredentials(insecure.NewCredentials()),
    )
    if err != nil {
        log.Fatalf("Bağlantı hatası: %v", err)
    }
    defer conn.Close()

    client := pb.NewUserServiceClient(conn)

    // 5 saniyelik timeout context
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    // Kullanıcı oluştur
    createResp, err := client.CreateUser(ctx, &pb.CreateUserRequest{
        Name:  "Ahmet Yilmaz",
        Email: "[email protected]",
        Age:   30,
    })
    if err != nil {
        log.Fatalf("CreateUser hatası: %v", err)
    }
    log.Printf("Oluşturuldu: %+v", createResp.User)

    // Kullanıcıyı getir
    getResp, err := client.GetUser(ctx, &pb.GetUserRequest{Id: createResp.User.Id})
    if err != nil {
        log.Fatalf("GetUser hatası: %v", err)
    }
    log.Printf("Getirildi: %+v", getResp.User)
}
EOF

go run client/main.go

grpcurl ile API Test Etmek

REST’te Postman veya curl kullanırsınız. gRPC için grpcurl aracı son derece kullanışlı:

# grpcurl kurulumu
# Linux için:
curl -sSL "https://github.com/fullstorydev/grpcurl/releases/download/v1.8.9/grpcurl_1.8.9_linux_x86_64.tar.gz" | tar -xz
sudo mv grpcurl /usr/local/bin/

# Mevcut servisleri listele (reflection açık olmalı)
grpcurl -plaintext localhost:50051 list

# Servis metodlarını listele
grpcurl -plaintext localhost:50051 list user.UserService

# Kullanıcı oluştur
grpcurl -plaintext -d '{"name":"Mehmet Demir","email":"[email protected]","age":25}' 
    localhost:50051 user.UserService/CreateUser

# Kullanıcı getir
grpcurl -plaintext -d '{"id":1}' 
    localhost:50051 user.UserService/GetUser

# Performans testi için hey veya ghz kullanabilirsiniz
# ghz kurulumu ve test
go install github.com/bojand/ghz/cmd/ghz@latest
ghz --insecure 
    --proto proto/user.proto 
    --call user.UserService.GetUser 
    -d '{"id":1}' 
    -n 1000 
    -c 50 
    localhost:50051

Streaming ile Gerçek Zamanlı Veri Akışı

gRPC’nin REST’e göre en güçlü olduğu alan streaming. Dört tip streaming var:

  • Unary: Tek istek, tek yanıt (klasik RPC)
  • Server Streaming: Tek istek, çoklu yanıt akışı
  • Client Streaming: Çoklu istek, tek yanıt
  • Bidirectional Streaming: Her iki taraftan da veri akışı

Log streaming için server-side streaming mükemmel bir kullanım senaryosudur. Diyelim ki sistem loglarını gerçek zamanlı istemciye aktarıyorsunuz:

# Streaming proto tanımı ekleyelim
cat >> proto/user.proto << 'EOF'

// Log streaming için ek tanımlar
message LogEntry {
  string timestamp = 1;
  string level = 2;
  string message = 3;
  string service = 4;
}

message LogStreamRequest {
  string service_name = 1;
  string min_level = 2;
}

service LogService {
  // Server-side streaming: client bir istek yapar, server sürekli log gönderir
  rpc StreamLogs(LogStreamRequest) returns (stream LogEntry);
}
EOF

# Log server implementasyonu
cat > server/log_server.go << 'EOF'
package main

import (
    "fmt"
    "log"
    "time"

    pb "github.com/sysadmin/grpc-user-service/proto"
)

type LogServer struct {
    pb.UnimplementedLogServiceServer
}

func (s *LogServer) StreamLogs(req *pb.LogStreamRequest, stream pb.LogService_StreamLogsServer) error {
    log.Printf("Log streaming başlatıldı: servis=%s, seviye=%s", req.ServiceName, req.MinLevel)

    // Gerçek senaryoda burası dosyayı tail -f gibi okur
    // Örnek olarak simüle ediyoruz
    levels := []string{"INFO", "WARN", "ERROR", "DEBUG"}
    messages := []string{
        "Bağlantı kuruldu",
        "İstek işlendi",
        "Yavaş sorgu tespit edildi",
        "Cache miss",
        "Retry yapılıyor",
    }

    for i := 0; i < 50; i++ {
        // Stream context iptal edildiyse dur
        if err := stream.Context().Err(); err != nil {
            return err
        }

        entry := &pb.LogEntry{
            Timestamp: time.Now().Format(time.RFC3339),
            Level:     levels[i%len(levels)],
            Message:   fmt.Sprintf("[%s] %s", req.ServiceName, messages[i%len(messages)]),
            Service:   req.ServiceName,
        }

        if err := stream.Send(entry); err != nil {
            return fmt.Errorf("stream gönderme hatası: %v", err)
        }

        time.Sleep(200 * time.Millisecond)
    }

    return nil
}
EOF

echo "Log server kodu oluşturuldu"

TLS ve Güvenlik Yapılandırması

Production ortamında gRPC bağlantılarınızı mutlaka TLS ile şifrelemeniz gerekiyor. Aksi halde microservice’ler arası trafik açık gezebilir:

# Self-signed sertifika oluştur (test için)
mkdir -p certs && cd certs

# CA key ve sertifikası
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 365 -key ca.key 
    -subj "/C=TR/O=SysAdmin Corp/CN=gRPC CA" 
    -out ca.crt

# Server key ve CSR
openssl genrsa -out server.key 4096
openssl req -new -key server.key 
    -subj "/C=TR/O=SysAdmin Corp/CN=localhost" 
    -out server.csr

# SAN (Subject Alternative Name) ile sertifika imzala
cat > server-ext.cnf << 'EOF'
[SAN]
subjectAltName=DNS:localhost,DNS:grpc-server,IP:127.0.0.1
EOF

openssl x509 -req -days 365 -in server.csr 
    -CA ca.crt -CAkey ca.key -CAcreateserial 
    -extfile server-ext.cnf -extensions SAN 
    -out server.crt

cd ..

# TLS'li server başlatma kodu
cat > server/tls_main.go << 'EOF'
package main

import (
    "log"
    "net"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
)

func startTLSServer() {
    creds, err := credentials.NewServerTLSFromFile("certs/server.crt", "certs/server.key")
    if err != nil {
        log.Fatalf("TLS sertifika yükleme hatası: %v", err)
    }

    grpcServer := grpc.NewServer(
        grpc.Creds(creds),
        // Maksimum mesaj boyutu: 10MB
        grpc.MaxRecvMsgSize(10*1024*1024),
        grpc.MaxSendMsgSize(10*1024*1024),
    )

    lis, err := net.Listen("tcp", ":50052")
    if err != nil {
        log.Fatalf("Dinleme hatası: %v", err)
    }

    log.Println("TLS gRPC server :50052 portunda başlatıldı")
    grpcServer.Serve(lis)
}
EOF

echo "TLS yapılandırması tamamlandı"

Kubernetes’te gRPC Load Balancing

gRPC ve Kubernetes birlikte kullanıldığında önemli bir detay var: HTTP/2 persistent connection özelliği nedeniyle standart Kubernetes Service load balancing çalışmaz. Tüm istekler aynı pod’a gider.

Bunu çözmek için ya headless service + client-side load balancing ya da Envoy/Linkerd gibi bir service mesh kullanmalısınız:

# Headless service tanımı
cat > k8s/grpc-service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
  name: grpc-user-service
  labels:
    app: grpc-user-service
spec:
  # clusterIP: None = headless service
  # Bu sayede DNS, pod IP'lerini direkt döner
  clusterIP: None
  selector:
    app: grpc-user-service
  ports:
  - name: grpc
    port: 50051
    targetPort: 50051
    protocol: TCP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: grpc-user-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: grpc-user-service
  template:
    metadata:
      labels:
        app: grpc-user-service
    spec:
      containers:
      - name: user-service
        image: your-registry/grpc-user-service:latest
        ports:
        - containerPort: 50051
        # gRPC health check
        readinessProbe:
          exec:
            command:
            - /bin/grpc_health_probe
            - -addr=:50051
          initialDelaySeconds: 5
          periodSeconds: 10
        livenessProbe:
          exec:
            command:
            - /bin/grpc_health_probe
            - -addr=:50051
          initialDelaySeconds: 10
          periodSeconds: 30
EOF

# Kubernetes'e uygula
kubectl apply -f k8s/grpc-service.yaml

# Pod durumlarını kontrol et
kubectl get pods -l app=grpc-user-service
kubectl logs -l app=grpc-user-service --tail=50

gRPC vs REST: Hangisini Seçmeli

Hem gRPC hem REST’in doğru kullanım alanları var. İkisini körce karşılaştırmak yerine senaryoya göre düşünmek gerekiyor:

gRPC tercih edin:

  • Microservice’ler arası dahili iletişimde (en güçlü kullanım alanı)
  • Düşük latency kritik olduğunda (trading sistemleri, real-time oyunlar)
  • Güçlü tip güvenliği ihtiyacınız olduğunda
  • Streaming veri aktarımında (log aggregation, telemetri, canlı metrikler)
  • Çok dilli sistemlerde (protobuf her major dili destekler)
  • Bant genişliği pahalı olduğunda (IoT cihazlar, mobil arka uç)

REST tercih edin:

  • Public API’ler için (herkes curl ile test edebilir)
  • Browser’dan direkt tüketim gerektiğinde (gRPC-Web olmadan çalışmaz)
  • Basit CRUD operasyonları için
  • API gateway’ler ve üçüncü parti entegrasyonlar için
  • Önbellekleme (CDN level caching) kritik olduğunda
  • Team’inizde protobuf bilgisi yoksa

Gerçek dünya konfigürasyonunda çoğu şirket hibrit yaklaşım kullanır: dış dünyaya REST API, servisler arası iletişimde gRPC. Bu yaklaşım hem developer experience’ı hem de performance’ı optimize eder.

Monitoring ve Observability

gRPC servislerini izlemek için Prometheus entegrasyonu şart:

# go-grpc-prometheus middleware kurulumu
go get github.com/grpc-ecosystem/go-grpc-prometheus

# Prometheus metrics ile server
cat > server/metrics_server.go << 'EOF'
package main

import (
    "net"
    "net/http"
    "log"

    grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
    "google.golang.org/grpc"
)

func startWithMetrics() {
    grpcServer := grpc.NewServer(
        grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor),
        grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor),
    )

    // gRPC server'ı başlat
    lis, _ := net.Listen("tcp", ":50051")
    go grpcServer.Serve(lis)

    // Prometheus metrics endpoint'i
    grpc_prometheus.EnableHandlingTimeHistogram()
    http.Handle("/metrics", promhttp.Handler())
    log.Println("Metrics :9090/metrics adresinde")
    http.ListenAndServe(":9090", nil)
}
EOF

# Prometheus config
cat > prometheus.yml << 'EOF'
scrape_configs:
  - job_name: 'grpc-user-service'
    static_configs:
      - targets: ['localhost:9090']
    scrape_interval: 15s
EOF

# Docker ile Prometheus başlat
docker run -d 
    --name prometheus 
    --network host 
    -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml 
    prom/prometheus

echo "Prometheus http://localhost:9090 adresinde çalışıyor"

Grafana dashboard’larında izlemeniz gereken kritik gRPC metrikleri şunlar:

  • grpc_server_handled_total: Toplam işlenen RPC sayısı (status code’a göre grupla)
  • grpc_server_handling_seconds: RPC işlem süresi histogramı
  • grpc_server_started_total: Başlatılan RPC sayısı (akıştaki RPC’leri hesapla)
  • grpc_server_msg_received_total: Alınan mesaj sayısı
  • grpc_server_msg_sent_total: Gönderilen mesaj sayısı

Sonuç

gRPC, özellikle microservice mimarisinde ciddi bir oyun değiştirici. HTTP/2 ve protobuf kombinasyonu sayesinde REST API’lere kıyasla genellikle 5-10 kat daha az bant genişliği kullanır ve 2-3 kat daha düşük latency sunar. Streaming desteği, güçlü tip güvenliği ve otomatik kod üretimi de üstüne cabası.

Ancak gRPC her derde deva değil. Public API’leriniz için REST’i bırakmanıza gerek yok. Pragmatik yaklaşım şu: dış dünyayla konuşurken REST, servisler arası konuşurken gRPC. Bu hibrit yaklaşımla hem geliştirici deneyimini hem de sistem performansını maksimize edersiniz.

Eğer bugün bir microservice projesi başlatıyorsanız, gRPC’yi başından beri tasarıma dahil etmenizi kesinlikle öneririm. Sonradan REST’ten gRPC’ye geçiş yapmak, başından yazmaktan çok daha zahmetli. .proto dosyalarınızı bir Git repository’sinde merkezi olarak yönetin, tüm takımların aynı kontrata göre çalışmasını sağlayın. Bu yaklaşım hem servislerin birbirinden bağımsız gelişmesine hem de sözleşme uyumluluğunun korunmasına yardımcı olur.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir