İçerikler

Golang ile gRPC'nin Gücü: Yüksek Performanslı Mikroservis İletişimi

İçerikler

Golang ile gRPC’nin Gücü: Yüksek Performanslı Mikroservis İletişimi için Modern Yaklaşım

Özet

  • gRPC: Google tarafından geliştirilen, HTTP/2 ve Protocol Buffers kullanan yüksek performanslı RPC framework’ü
  • Go + gRPC: Eşzamanlılık ve performans için ideal kombinasyon
  • 4 İletişim Modeli: Unary, Server Streaming, Client Streaming, Bidirectional Streaming
  • Production Ready: Auth, Load Balancing, Health Checks, Monitoring desteği

1. Giriş: Mikroservis Çağında İletişim Sorununa Modern Çözüm

Modern yazılım ekosisteminde mikroservis mimarileri, monolitik yapıların sınırlamalarını aşmak için adeta bir devrim yarattı. Ancak bu dağıtık yapı, bir kritik soruyu gündeme getiriyor: “Servisler birbirleriyle nasıl verimli, güvenli ve güvenilir bir şekilde iletişim kuracak?”

İşte tam bu noktada gRPC (Google Remote Procedure Call), mikroservis iletişiminde bir oyun değiştirici olarak karşımıza çıkıyor. Google tarafından geliştirilen bu açık kaynaklı yüksek performanslı RPC çatısı, modern dağıtık sistemlerin iletişim ihtiyaçlarını karşılamak üzere tasarlandı.

2. gRPC’nin Teknik Temelleri: Neden Bu Kadar Etkili?

gRPC’nin gücü, üç temel teknolojik bileşenin sinerjisinden geliyor:

2.1 HTTP/2 Protokolü

Geleneksel REST API’lerin kullandığı HTTP/1.1’in sınırlamalarını aşarak:

  • Çoklama (Multiplexing): Tek bir TCP bağlantısı üzerinden paralel istekler
  • İkili (Binary) Format: Daha az bant genişliği, daha hızlı iletişim
  • Sunucu Anında İtme (Server Push): İstemci talebi beklemeden veri gönderebilme
  • Header Compression: HPACK ile başlık sıkıştırma

2.2 Protocol Buffers (protobuf)

JSON/XML’e kıyasla:

  • %60-80 daha küçük veri boyutu: Daha az bant genişliği kullanımı
  • %20-100 daha hızlı serileştirme/ters serileştirme: Daha düşük CPU kullanımı
  • Tip güvenliği: Derleme zamanında hata yakalama
  • Otomatik kod üretimi: .proto dosyalarından çoklu dil desteği
  • Versiyonlama desteği: Geriye dönük uyumluluk

2.3 Strongly-Typed Interface

.proto dosyaları ile servis kontratları, tıpkı bir API şeması gibi, hem sunucu hem istemci için tutarlılık sağlıyor. Bu kontratlar:

  • Derleme zamanında tip kontrolü sağlar
  • Dokümantasyon görevi görür
  • Kod üretimi için kaynak olur
  • Versiyon yönetimi için merkezi bir nokta sunar

3. gRPC Nasıl Çalışır? Arkasındaki Sihir

gRPC, Remote Procedure Call mantığına dayanır. Yani bir servisteki fonksiyonu, sanki yerel bir fonksiyonmuş gibi, ağ üzerinden uzak bir serviste çağırabilirsiniz. Bu soyutlama, geliştiricilere dağıtık sistem karmaşıklığını hissettirmeden yüksek performanslı iletişim imkanı sunar.

3.1 gRPC İş Akışı

4. gRPC İletişim Modelleri: Sadece Basit İstek-Yanıt Değil!

gRPC dört farklı iletişim modeli sunar:

Model Açıklama Kullanım Senaryosu Diyagram
Unary RPC Geleneksel istek-yanıt Basit API çağrıları, CRUD işlemleri Client → Server → Client
Server Streaming Sunucu veri akışı Gerçek zamanlı veri feed’leri, büyük veri setleri Client → Server → Server → Server → Client
Client Streaming İstemci veri akışı Büyük dosya yükleme, toplu veri gönderimi Client → Client → Client → Server → Client
Bidirectional Streaming Çift yönlü akış Canlı sohbet, online oyunlar, gerçek zamanlı senkronizasyon Client ↔ Server ↔ Client ↔ Server

4.1 İletişim Modelleri Görselleştirme

5. Neden Golang + gRPC = Mükemmel İkili? 🚀

Go dili ve gRPC, adeta birbirleri için yaratılmış iki teknoloji. İşte bu mükemmel uyumun nedenleri:

5.1 Yüksek Performans Uyumu

Go’nun hafif goroutine’leri ile gRPC’nin eşzamanlı işleme yeteneği mükemmel bir uyum sağlar:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Go'nun hafif goroutine'leri ile gRPC'nin eşzamanlı işleme yeteneği
func (s *server) StreamUsers(req *pb.UserFilter, stream pb.UserService_StreamUsersServer) error {
    for _, user := range fetchUsers(req.Keyword) {
        if err := stream.Send(user); err != nil {
            return err
        }
        // Her goroutine hafif - binlerce eşzamanlı bağlantı!
    }
    return nil
}

5.2 Yerleşik Eşzamanlılık Desteği

Go’nun goroutine ve channel yapıları, gRPC’nin streaming özellikleriyle kusursuz entegre olur. Binlerce eşzamanlı bağlantıyı minimum kaynak tüketimiyle yönetebilirsiniz.

5.3 Zengin Standart Kütüphane

Go’nun networking ve concurrency kütüphaneleri, gRPC implementasyonunu son derece temiz ve verimli hale getiriyor.

5.4 Cross-Platform Tutarlılık

Tek bir .proto dosyasından Go, Java, Python, C++ ve daha birçok dil için tutarlı istemciler üretilebilir.

6. Hands-On: Gerçek Dünya Örneği ile gRPC Implementasyonu

6.0 Kurulum ve Protobuf Compiler

Adım 1: Protobuf Compiler Kurulumu

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# macOS
brew install protobuf

# Linux
apt-get install -y protobuf-compiler

# Go plugin
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 ekle
export PATH="$PATH:$(go env GOPATH)/bin"

Adım 2: Kod Üretimi

1
2
3
4
# Proto dosyasından Go kodu üret
protoc --go_out=. --go_opt=paths=source_relative \
       --go-grpc_out=. --go-grpc_opt=paths=source_relative \
       user.proto

6.1 Adım 1: Proto Dosyası Tasarımı - API Kontratımız

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
syntax = "proto3";

package user;

import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";

option go_package = "example.com/user;user";

// Servis tanımı - API'mizin arayüzü
service UserService {
  // Basit istek-yanıt
  rpc GetUser (UserRequest) returns (UserResponse);
  
  // Kullanıcı oluşturma
  rpc CreateUser (CreateUserRequest) returns (UserResponse);
  
  // Kullanıcı güncelleme
  rpc UpdateUser (UpdateUserRequest) returns (UserResponse);
  
  // Kullanıcı silme
  rpc DeleteUser (UserRequest) returns (google.protobuf.Empty);
  
  // Sunucu tarafı streaming - kullanıcı listesi
  rpc StreamUsers (UserFilter) returns (stream UserResponse);
  
  // İstemci tarafı streaming - toplu kullanıcı oluşturma
  rpc CreateUsers (stream CreateUserRequest) returns (BulkCreateResponse);
  
  // Çift yönlü streaming - gerçek zamanlı mesajlaşma
  rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}

// Mesaj yapıları
message UserRequest {
  int32 id = 1;  // Alan numaraları - ASLA değiştirmeyin!
}

message UserResponse {
  int32 id = 1;
  string name = 2;
  string email = 3;
  string role = 4;
  google.protobuf.Timestamp created_at = 5;
  google.protobuf.Timestamp updated_at = 6;
}

message CreateUserRequest {
  string name = 1;
  string email = 2;
  string password = 3;
  string role = 4;
}

message UpdateUserRequest {
  int32 id = 1;
  string name = 2;
  string email = 3;
  string role = 4;
}

message UserFilter {
  string keyword = 1;
  int32 limit = 2;
  int32 offset = 3;
  repeated string roles = 4;
}

message BulkCreateResponse {
  int32 created_count = 1;
  repeated int32 user_ids = 2;
  repeated string errors = 3;
}

message ChatMessage {
  string from = 1;
  string to = 2;
  string message = 3;
  google.protobuf.Timestamp timestamp = 4;
  MessageType type = 5;
}

enum MessageType {
  MESSAGE_TYPE_UNKNOWN = 0;
  MESSAGE_TYPE_TEXT = 1;
  MESSAGE_TYPE_FILE = 2;
  MESSAGE_TYPE_IMAGE = 3;
}

6.2 Adım 2: Go Sunucu Implementasyonu - Production Ready

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
package main

import (
	"context"
	"fmt"
	"log"
	"net"
	"sync"
	"time"

	pb "example.com/user"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/reflection"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"google.golang.org/protobuf/types/known/timestamppb"
)

// Veritabanı mock'u
var (
	mockUsers = []*pb.UserResponse{
		{
			Id:        1,
			Name:      "Ahmet Yılmaz",
			Email:     "ahmet@example.com",
			Role:      "admin",
			CreatedAt: timestamppb.New(time.Now()),
			UpdatedAt: timestamppb.New(time.Now()),
		},
		{
			Id:        2,
			Name:      "Ayşe Demir",
			Email:     "ayse@example.com",
			Role:      "user",
			CreatedAt: timestamppb.New(time.Now()),
			UpdatedAt: timestamppb.New(time.Now()),
		},
		{
			Id:        3,
			Name:      "Mehmet Kaya",
			Email:     "mehmet@example.com",
			Role:      "user",
			CreatedAt: timestamppb.New(time.Now()),
			UpdatedAt: timestamppb.New(time.Now()),
		},
	}
	userMutex sync.RWMutex
	nextID    int32 = 4
)

type server struct {
	pb.UnimplementedUserServiceServer
}

// GetUser - Unary RPC implementasyonu
func (s *server) GetUser(ctx context.Context, req *pb.UserRequest) (*pb.UserResponse, error) {
	log.Printf("GetUser çağrıldı: ID=%d", req.Id)
	
	// Context kontrolü - timeout/cancel durumlarını handle et
	if ctx.Err() == context.DeadlineExceeded {
		return nil, status.Error(codes.DeadlineExceeded, "İstek zaman aşımına uğradı")
	}
	
	if ctx.Err() == context.Canceled {
		return nil, status.Error(codes.Canceled, "İstek iptal edildi")
	}
	
	userMutex.RLock()
	defer userMutex.RUnlock()
	
	// Kullanıcı bul
	for _, user := range mockUsers {
		if user.Id == req.Id {
			return user, nil
		}
	}
	
	// Kullanıcı bulunamazsa hata döndür
	return nil, status.Errorf(codes.NotFound, "Kullanıcı ID=%d bulunamadı", req.Id)
}

// CreateUser - Kullanıcı oluşturma
func (s *server) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.UserResponse, error) {
	log.Printf("CreateUser çağrıldı: email=%s", req.Email)
	
	// Validasyon
	if req.Name == "" || req.Email == "" {
		return nil, status.Error(codes.InvalidArgument, "İsim ve email zorunludur")
	}
	
	userMutex.Lock()
	defer userMutex.Unlock()
	
	// Email kontrolü
	for _, user := range mockUsers {
		if user.Email == req.Email {
			return nil, status.Errorf(codes.AlreadyExists, "Email %s zaten kullanılıyor", req.Email)
		}
	}
	
	now := time.Now()
	newUser := &pb.UserResponse{
		Id:        nextID,
		Name:      req.Name,
		Email:     req.Email,
		Role:      req.Role,
		CreatedAt: timestamppb.New(now),
		UpdatedAt: timestamppb.New(now),
	}
	
	if newUser.Role == "" {
		newUser.Role = "user"
	}
	
	mockUsers = append(mockUsers, newUser)
	nextID++
	
	return newUser, nil
}

// UpdateUser - Kullanıcı güncelleme
func (s *server) UpdateUser(ctx context.Context, req *pb.UpdateUserRequest) (*pb.UserResponse, error) {
	log.Printf("UpdateUser çağrıldı: ID=%d", req.Id)
	
	userMutex.Lock()
	defer userMutex.Unlock()
	
	for i, user := range mockUsers {
		if user.Id == req.Id {
			if req.Name != "" {
				mockUsers[i].Name = req.Name
			}
			if req.Email != "" {
				mockUsers[i].Email = req.Email
			}
			if req.Role != "" {
				mockUsers[i].Role = req.Role
			}
			mockUsers[i].UpdatedAt = timestamppb.New(time.Now())
			return mockUsers[i], nil
		}
	}
	
	return nil, status.Errorf(codes.NotFound, "Kullanıcı ID=%d bulunamadı", req.Id)
}

// DeleteUser - Kullanıcı silme
func (s *server) DeleteUser(ctx context.Context, req *pb.UserRequest) (*emptypb.Empty, error) {
	log.Printf("DeleteUser çağrıldı: ID=%d", req.Id)
	
	userMutex.Lock()
	defer userMutex.Unlock()
	
	for i, user := range mockUsers {
		if user.Id == req.Id {
			mockUsers = append(mockUsers[:i], mockUsers[i+1:]...)
			return &emptypb.Empty{}, nil
		}
	}
	
	return nil, status.Errorf(codes.NotFound, "Kullanıcı ID=%d bulunamadı", req.Id)
}

// StreamUsers - Server Streaming RPC implementasyonu
func (s *server) StreamUsers(filter *pb.UserFilter, stream pb.UserService_StreamUsersServer) error {
	log.Printf("StreamUsers çağrıldı: filter=%v", filter)
	
	userMutex.RLock()
	users := make([]*pb.UserResponse, len(mockUsers))
	copy(users, mockUsers)
	userMutex.RUnlock()
	
	for i, user := range users {
		// Filtreleme
		if filter.Keyword != "" && !contains(user.Name, filter.Keyword) {
			continue
		}
		
		// Role filtresi
		if len(filter.Roles) > 0 {
			found := false
			for _, role := range filter.Roles {
				if user.Role == role {
					found = true
					break
				}
			}
			if !found {
				continue
			}
		}
		
		// Limit kontrolü
		if filter.Limit > 0 && i >= int(filter.Limit) {
			break
		}
		
		// Kullanıcıyı stream'e gönder
		if err := stream.Send(user); err != nil {
			return err
		}
		
		// Yapay gecikme - gerçek dünyada veritabanı/network işlemleri
		time.Sleep(100 * time.Millisecond)
	}
	
	return nil
}

// CreateUsers - Client Streaming RPC implementasyonu
func (s *server) CreateUsers(stream pb.UserService_CreateUsersServer) error {
	log.Println("CreateUsers çağrıldı - client streaming")
	
	var createdCount int32
	var userIDs []int32
	var errors []string
	
	for {
		req, err := stream.Recv()
		if err != nil {
			if err.Error() == "EOF" {
				break
			}
			return err
		}
		
		// Kullanıcı oluştur
		user, err := s.CreateUser(stream.Context(), req)
		if err != nil {
			errors = append(errors, fmt.Sprintf("Email %s: %v", req.Email, err))
			continue
		}
		
		createdCount++
		userIDs = append(userIDs, user.Id)
	}
	
	// Sonuçları gönder
	return stream.SendAndClose(&pb.BulkCreateResponse{
		CreatedCount: createdCount,
		UserIds:      userIDs,
		Errors:       errors,
	})
}

// Chat - Bidirectional Streaming RPC implementasyonu
func (s *server) Chat(stream pb.UserService_ChatServer) error {
	log.Println("Chat çağrıldı - bidirectional streaming")
	
	var wg sync.WaitGroup
	wg.Add(2)
	
	// Mesaj alma goroutine
	go func() {
		defer wg.Done()
		for {
			msg, err := stream.Recv()
			if err != nil {
				return
			}
			log.Printf("Mesaj alındı: %s -> %s: %s", msg.From, msg.To, msg.Message)
			
			// Echo gönder
			response := &pb.ChatMessage{
				From:      "Server",
				To:        msg.From,
				Message:   fmt.Sprintf("Echo: %s", msg.Message),
				Timestamp: timestamppb.New(time.Now()),
				Type:      pb.MessageType_MESSAGE_TYPE_TEXT,
			}
			
			if err := stream.Send(response); err != nil {
				return
			}
		}
	}()
	
	// Heartbeat gönderme goroutine
	go func() {
		defer wg.Done()
		ticker := time.NewTicker(30 * time.Second)
		defer ticker.Stop()
		
		for {
			select {
			case <-ticker.C:
				heartbeat := &pb.ChatMessage{
					From:      "Server",
					Message:   "Heartbeat",
					Timestamp: timestamppb.New(time.Now()),
					Type:      pb.MessageType_MESSAGE_TYPE_TEXT,
				}
				if err := stream.Send(heartbeat); err != nil {
					return
				}
			case <-stream.Context().Done():
				return
			}
		}
	}()
	
	wg.Wait()
	return nil
}

// contains - basit string arama
func contains(s, substr string) bool {
	return len(s) >= len(substr) && (s[:len(substr)] == substr || 
		len(s) > len(substr) && s[len(substr):] == substr)
}

func main() {
	// TCP listener oluştur
	lis, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("50051 portu dinlenemedi: %v", err)
	}
	
	// gRPC sunucusu oluştur
	grpcServer := grpc.NewServer(
		grpc.MaxConcurrentStreams(1000), // Eşzamanlı stream limiti
		grpc.MaxRecvMsgSize(4*1024*1024), // 4MB max mesaj boyutu
		grpc.MaxSendMsgSize(4*1024*1024),
	)
	
	// Servisi kaydet
	pb.RegisterUserServiceServer(grpcServer, &server{})
	
	// Reflection - debug için
	reflection.Register(grpcServer)
	
	log.Println("🚀 gRPC Sunucusu 50051 portunda başlatıldı...")
	log.Println("📍 Health Check: grpc_health_probe -addr=localhost:50051")
	log.Println("📍 API Keşfi: grpcurl -plaintext localhost:50051 list")
	
	// Sunucuyu başlat
	if err := grpcServer.Serve(lis); err != nil {
		log.Fatalf("gRPC sunucusu başlatılamadı: %v", err)
	}
}

6.3 Adım 3: Go İstemci Implementasyonu - Best Practices

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"sync"
	"time"

	pb "example.com/user"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/metadata"
	"google.golang.org/protobuf/types/known/timestamppb"
)

func main() {
	// gRPC bağlantısı oluştur
	conn, err := grpc.Dial(
		"localhost:50051",
		grpc.WithTransportCredentials(insecure.NewCredentials()), // Production'da TLS kullan!
		grpc.WithBlock(), // Bağlantı kurulana kadar bekle
		grpc.WithTimeout(5*time.Second), // Bağlantı timeout'u
	)
	if err != nil {
		log.Fatalf("Sunucuya bağlanılamadı: %v", err)
	}
	defer conn.Close()
	
	// gRPC istemcisi oluştur
	client := pb.NewUserServiceClient(conn)
	
	// Unary RPC örneği
	callUnaryRPC(client)
	
	// Server Streaming RPC örneği
	callStreamingRPC(client)
	
	// Client Streaming RPC örneği
	callClientStreamingRPC(client)
	
	// Bidirectional Streaming RPC örneği
	callBidirectionalStreamingRPC(client)
}

func callUnaryRPC(client pb.UserServiceClient) {
	log.Println("=== Unary RPC Örneği ===")
	
	// Context oluştur (5 saniye timeout)
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	
	// Metadata ekle
	ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer token123")
	
	// gRPC çağrısı yap
	res, err := client.GetUser(ctx, &pb.UserRequest{Id: 1})
	if err != nil {
		log.Printf("GetUser hatası: %v", err)
		return
	}
	
	log.Printf("✅ Kullanıcı bilgileri: %s (%s) - %s", 
		res.Name, res.Email, res.Role)
}

func callStreamingRPC(client pb.UserServiceClient) {
	log.Println("=== Server Streaming RPC Örneği ===")
	
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	
	// Streaming çağrısı başlat
	stream, err := client.StreamUsers(ctx, &pb.UserFilter{
		Keyword: "Ahmet",
		Limit:   10,
	})
	if err != nil {
		log.Printf("StreamUsers başlatma hatası: %v", err)
		return
	}
	
	// Stream'den gelen verileri işle
	for {
		user, err := stream.Recv()
		if err == io.EOF {
			log.Println("✅ Stream sonu - tüm kullanıcılar alındı")
			break
		}
		if err != nil {
			log.Printf("Stream okuma hatası: %v", err)
			break
		}
		
		log.Printf("📨 Stream'den kullanıcı: %s - %s", user.Name, user.Email)
	}
}

func callClientStreamingRPC(client pb.UserServiceClient) {
	log.Println("=== Client Streaming RPC Örneği ===")
	
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()
	
	stream, err := client.CreateUsers(ctx)
	if err != nil {
		log.Printf("CreateUsers başlatma hatası: %v", err)
		return
	}
	
	// Toplu kullanıcı gönder
	users := []*pb.CreateUserRequest{
		{Name: "Ali Veli", Email: "ali@example.com", Role: "user"},
		{Name: "Zeynep Kaya", Email: "zeynep@example.com", Role: "user"},
		{Name: "Can Demir", Email: "can@example.com", Role: "admin"},
	}
	
	for _, user := range users {
		if err := stream.Send(user); err != nil {
			log.Printf("Gönderme hatası: %v", err)
			return
		}
		log.Printf("📤 Kullanıcı gönderildi: %s", user.Email)
	}
	
	// Sonuçları al
	result, err := stream.CloseAndRecv()
	if err != nil {
		log.Printf("Sonuç alma hatası: %v", err)
		return
	}
	
	log.Printf("✅ %d kullanıcı oluşturuldu, %d hata", 
		result.CreatedCount, len(result.Errors))
}

func callBidirectionalStreamingRPC(client pb.UserServiceClient) {
	log.Println("=== Bidirectional Streaming RPC Örneği ===")
	
	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()
	
	stream, err := client.Chat(ctx)
	if err != nil {
		log.Printf("Chat başlatma hatası: %v", err)
		return
	}
	
	var wg sync.WaitGroup
	wg.Add(2)
	
	// Mesaj gönderme goroutine
	go func() {
		defer wg.Done()
		for i := 0; i < 5; i++ {
			msg := &pb.ChatMessage{
				From:      "Client",
				To:        "Server",
				Message:   fmt.Sprintf("Mesaj %d", i+1),
				Timestamp: timestamppb.New(time.Now()),
				Type:      pb.MessageType_MESSAGE_TYPE_TEXT,
			}
			
			if err := stream.Send(msg); err != nil {
				log.Printf("Gönderme hatası: %v", err)
				return
			}
			time.Sleep(1 * time.Second)
		}
		stream.CloseSend()
	}()
	
	// Mesaj alma goroutine
	go func() {
		defer wg.Done()
		for {
			msg, err := stream.Recv()
			if err == io.EOF {
				return
			}
			if err != nil {
				log.Printf("Alma hatası: %v", err)
				return
			}
			log.Printf("📥 Mesaj alındı: %s -> %s: %s", 
				msg.From, msg.To, msg.Message)
		}
	}()
	
	wg.Wait()
	log.Println("✅ Chat tamamlandı")
}

7. İleri Seviye gRPC: Production Ready Özellikler

7.1 Güvenlik (Authentication & Authorization)

JWT Tabanlı Kimlik Doğrulama

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package main

import (
	"context"
	"strings"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

// JWT Interceptor
func JWTUnaryInterceptor() grpc.UnaryServerInterceptor {
	return func(
		ctx context.Context,
		req interface{},
		info *grpc.UnaryServerInfo,
		handler grpc.UnaryHandler,
	) (interface{}, error) {
		// Public endpoint kontrolü
		if isPublicEndpoint(info.FullMethod) {
			return handler(ctx, req)
		}
		
		// Metadata'dan token al
		meta, ok := metadata.FromIncomingContext(ctx)
		if !ok {
			return nil, status.Error(codes.Unauthenticated, "metadata eksik")
		}
		
		tokens := meta.Get("authorization")
		if len(tokens) == 0 {
			return nil, status.Error(codes.Unauthenticated, "token eksik")
		}
		
		// Bearer token parse et
		token := strings.TrimPrefix(tokens[0], "Bearer ")
		
		// JWT validasyonu
		claims, err := validateJWT(token)
		if err != nil {
			return nil, status.Error(codes.Unauthenticated, "geçersiz token")
		}
		
		// Context'e kullanıcı bilgilerini ekle
		ctx = context.WithValue(ctx, "user", claims)
		
		return handler(ctx, req)
	}
}

func isPublicEndpoint(method string) bool {
	publicMethods := []string{
		"/user.UserService/GetUser", // Örnek public endpoint
	}
	for _, m := range publicMethods {
		if method == m {
			return true
		}
	}
	return false
}

func validateJWT(token string) (*UserClaims, error) {
	// JWT validasyon implementasyonu
	// jwt-go veya benzeri bir kütüphane kullanın
	return &UserClaims{UserID: "123", Role: "admin"}, nil
}

type UserClaims struct {
	UserID string
	Role   string
}

Role-Based Authorization

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
func RequireRole(roles ...string) grpc.UnaryServerInterceptor {
	return func(
		ctx context.Context,
		req interface{},
		info *grpc.UnaryServerInfo,
		handler grpc.UnaryHandler,
	) (interface{}, error) {
		claims, ok := ctx.Value("user").(*UserClaims)
		if !ok {
			return nil, status.Error(codes.Unauthenticated, "kullanıcı bilgisi bulunamadı")
		}
		
		for _, role := range roles {
			if claims.Role == role {
				return handler(ctx, req)
			}
		}
		
		return nil, status.Error(codes.PermissionDenied, "yetersiz yetki")
	}
}

7.2 Interceptors - Middleware Pattern

Logging Interceptor

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func LoggingUnaryInterceptor() grpc.UnaryServerInterceptor {
	return func(
		ctx context.Context,
		req interface{},
		info *grpc.UnaryServerInfo,
		handler grpc.UnaryHandler,
	) (interface{}, error) {
		start := time.Now()
		
		resp, err := handler(ctx, req)
		
		duration := time.Since(start)
		log.Printf("Method: %s, Duration: %v, Error: %v",
			info.FullMethod, duration, err)
		
		return resp, err
	}
}

Metrics Interceptor

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import (
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
)

var (
	grpcRequestsTotal = promauto.NewCounterVec(
		prometheus.CounterOpts{
			Name: "grpc_requests_total",
			Help: "Total number of gRPC requests",
		},
		[]string{"method", "status"},
	)
	
	grpcRequestDuration = promauto.NewHistogramVec(
		prometheus.HistogramOpts{
			Name:    "grpc_request_duration_seconds",
			Help:    "gRPC request duration",
			Buckets: prometheus.DefBuckets,
		},
		[]string{"method"},
	)
)

func MetricsUnaryInterceptor() grpc.UnaryServerInterceptor {
	return func(
		ctx context.Context,
		req interface{},
		info *grpc.UnaryServerInfo,
		handler grpc.UnaryHandler,
	) (interface{}, error) {
		start := time.Now()
		
		resp, err := handler(ctx, req)
		
		duration := time.Since(start)
		status := "success"
		if err != nil {
			status = "error"
		}
		
		grpcRequestsTotal.WithLabelValues(info.FullMethod, status).Inc()
		grpcRequestDuration.WithLabelValues(info.FullMethod).Observe(duration.Seconds())
		
		return resp, err
	}
}

Recovery Interceptor

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
func RecoveryUnaryInterceptor() grpc.UnaryServerInterceptor {
	return func(
		ctx context.Context,
		req interface{},
		info *grpc.UnaryServerInfo,
		handler grpc.UnaryHandler,
	) (interface{}, error) {
		defer func() {
			if r := recover(); r != nil {
				log.Printf("Panic recovered: %v", r)
				// Stack trace logla
			}
		}()
		
		return handler(ctx, req)
	}
}

7.3 Health Checking

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import (
	"google.golang.org/grpc/health"
	"google.golang.org/grpc/health/grpc_health_v1"
)

func setupHealthCheck(grpcServer *grpc.Server) {
	healthServer := health.NewServer()
	grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
	
	// Servis durumunu ayarla
	healthServer.SetServingStatus("user.UserService", grpc_health_v1.HealthCheckResponse_SERVING)
}

7.4 Load Balancing & Service Discovery

Client-Side Load Balancing

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import (
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

// DNS-based service discovery with round-robin
conn, err := grpc.Dial(
	"dns:///my-service.namespace.svc.cluster.local:50051",
	grpc.WithTransportCredentials(insecure.NewCredentials()),
	grpc.WithDefaultServiceConfig(`{
		"loadBalancingPolicy": "round_robin",
		"methodConfig": [{
			"name": [{"service": "user.UserService"}],
			"waitForReady": true,
			"timeout": "10s"
		}]
	}`),
)

7.5 TLS/SSL Konfigürasyonu

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import (
	"crypto/tls"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

// Sunucu tarafı TLS
func setupTLSServer() (*grpc.Server, error) {
	creds, err := credentials.NewServerTLSFromFile(
		"server.crt",
		"server.key",
	)
	if err != nil {
		return nil, err
	}
	
	grpcServer := grpc.NewServer(grpc.Creds(creds))
	return grpcServer, nil
}

// İstemci tarafı TLS
func setupTLSClient() (*grpc.ClientConn, error) {
	config := &tls.Config{
		InsecureSkipVerify: false,
		ServerName:         "myservice.example.com",
	}
	
	creds := credentials.NewTLS(config)
	
	conn, err := grpc.Dial(
		"myservice.example.com:50051",
		grpc.WithTransportCredentials(creds),
	)
	return conn, err
}

7.6 Graceful Shutdown

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import (
	"os"
	"os/signal"
	"syscall"
)

func main() {
	// ... sunucu kurulumu ...
	
	// Graceful shutdown için signal handler
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
	
	go func() {
		<-sigChan
		log.Println("Shutdown signal alındı, graceful shutdown başlatılıyor...")
		
		// Graceful shutdown
		grpcServer.GracefulStop()
		
		log.Println("Sunucu başarıyla kapatıldı")
		os.Exit(0)
	}()
	
	// Sunucuyu başlat
	if err := grpcServer.Serve(lis); err != nil {
		log.Fatalf("gRPC sunucusu başlatılamadı: %v", err)
	}
}

8. Performans Optimizasyonu: gRPC’yi En İyi Şekilde Kullanma

8.1 Connection Pooling

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import (
	"sync"
	
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	
	pb "example.com/user"
)

type ClientPool struct {
	pool sync.Pool
}

func NewClientPool(addr string) *ClientPool {
	return &ClientPool{
		pool: sync.Pool{
			New: func() interface{} {
				conn, err := grpc.Dial(
					addr,
					grpc.WithTransportCredentials(insecure.NewCredentials()),
					grpc.WithMaxMsgSize(4*1024*1024), // 4MB
				)
				if err != nil {
					return nil
				}
				return pb.NewUserServiceClient(conn)
			},
		},
	}
}

func (p *ClientPool) Get() pb.UserServiceClient {
	return p.pool.Get().(pb.UserServiceClient)
}

func (p *ClientPool) Put(client pb.UserServiceClient) {
	p.pool.Put(client)
}

8.2 Streaming için Buffer Ayarları

1
2
3
4
5
6
7
grpcServer := grpc.NewServer(
	grpc.InitialWindowSize(65535),        // Initial window size
	grpc.InitialConnWindowSize(65535),    // Initial connection window size
	grpc.MaxRecvMsgSize(10*1024*1024),   // 10MB max receive
	grpc.MaxSendMsgSize(10*1024*1024),   // 10MB max send
	grpc.MaxConcurrentStreams(1000),      // Max concurrent streams
)

8.3 Timeout ve Retry Politikaları

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// Retry configuration
retryPolicy := `{
    "methodConfig": [{
        "name": [{"service": "user.UserService"}],
        "waitForReady": true,
        "timeout": "10s",
        "retryPolicy": {
            "MaxAttempts": 3,
            "InitialBackoff": "0.1s",
            "MaxBackoff": "1s",
            "BackoffMultiplier": 2.0,
            "RetryableStatusCodes": ["UNAVAILABLE", "DEADLINE_EXCEEDED"]
        }
    }]
}`

conn, err := grpc.Dial(
	"localhost:50051",
	grpc.WithTransportCredentials(insecure.NewCredentials()),
	grpc.WithDefaultServiceConfig(retryPolicy),
)

9. Monitoring ve Debugging

9.1 gRPC Health Probes

1
2
3
4
5
6
7
8
# Health check
grpc_health_probe -addr=localhost:50051

# Detaylı health check
grpc_health_probe -addr=localhost:50051 -service=user.UserService

# TLS ile
grpc_health_probe -addr=localhost:50051 -tls -tls-no-verify

9.2 gRPC Reflection

1
2
3
4
import "google.golang.org/grpc/reflection"

// Debug için reflection enable et
reflection.Register(grpcServer)
1
2
3
4
5
6
# grpcurl ile API keşfi
grpcurl -plaintext localhost:50051 list
grpcurl -plaintext localhost:50051 describe user.UserService

# Method çağrısı
grpcurl -plaintext -d '{"id": 1}' localhost:50051 user.UserService/GetUser

9.3 OpenTelemetry Integration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import (
	"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otgrpc"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/exporters/jaeger"
	"go.opentelemetry.io/otel/sdk/trace"
)

func setupTracing() (*trace.TracerProvider, error) {
	exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(
		jaeger.WithEndpoint("http://localhost:14268/api/traces"),
	))
	if err != nil {
		return nil, err
	}
	
	tp := trace.NewTracerProvider(
		trace.WithBatcher(exporter),
		trace.WithResource(resource.NewWithAttributes(
			semconv.SchemaURL,
			semconv.ServiceNameKey.String("user-service"),
		)),
	)
	
	otel.SetTracerProvider(tp)
	return tp, nil
}

// Server'da kullanım
grpcServer := grpc.NewServer(
	grpc.UnaryInterceptor(otgrpc.UnaryServerInterceptor()),
	grpc.StreamInterceptor(otgrpc.StreamServerInterceptor()),
)

9.4 Error Handling Best Practices

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import (
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// Doğru hata döndürme
func (s *server) GetUser(ctx context.Context, req *pb.UserRequest) (*pb.UserResponse, error) {
	// Validation hatası
	if req.Id <= 0 {
		return nil, status.Error(codes.InvalidArgument, "Geçersiz kullanıcı ID")
	}
	
	// Not found hatası
	user, err := s.repo.GetUser(ctx, req.Id)
	if err == ErrUserNotFound {
		return nil, status.Errorf(codes.NotFound, "Kullanıcı bulunamadı: %d", req.Id)
	}
	if err != nil {
		// Internal error - logla ama detay verme
		log.Printf("Internal error: %v", err)
		return nil, status.Error(codes.Internal, "İç sunucu hatası")
	}
	
	return user, nil
}

10. Gerçek Dünya Senaryoları: gRPC Nerede Parlıyor?

10.1 Financial Services

Yüksek frekanslı trading sistemleri için ideal:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
service TradingService {
    // Real-time market data streaming
    rpc StreamMarketData(MarketDataRequest) returns (stream MarketDataUpdate);
    
    // High-frequency trade execution
    rpc ExecuteTrade(stream TradeOrder) returns (TradeConfirmation);
    
    // Portfolio updates
    rpc StreamPortfolio(AccountRequest) returns (stream PortfolioUpdate);
}

10.2 IoT ve Real-time Sistemler

Milyonlarca cihazdan gelen veri akışı:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
service IoTService {
    // Sensor data ingestion
    rpc StreamSensorData(stream SensorReading) returns (Ack);
    
    // Bidirectional device control
    rpc BidirectionalCommand(
        stream DeviceCommand
    ) returns (stream DeviceResponse);
    
    // Batch data upload
    rpc UploadBatchData(stream BatchData) returns (UploadResponse);
}

10.3 Microservis Mesh

Service-to-service communication:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
service OrderService {
    rpc CreateOrder(OrderRequest) returns (OrderResponse);
    rpc GetOrder(OrderRequest) returns (OrderResponse);
}

service PaymentService {
    rpc ProcessPayment(PaymentRequest) returns (PaymentResponse);
    rpc RefundPayment(RefundRequest) returns (RefundResponse);
}

service InventoryService {
    rpc CheckStock(StockRequest) returns (StockResponse);
    rpc ReserveItems(ReserveRequest) returns (ReserveResponse);
}

11. Performans Karşılaştırması

11.1 REST vs gRPC Performans

Metrik REST (JSON) gRPC (Protobuf) İyileştirme
Serileştirme Süresi 100-200ms 10-50ms %75-90 azalma
Payload Boyutu 1.0x 0.2-0.4x %60-80 azalma
Throughput 1,000 req/s 5,000-10,000 req/s 5-10x artış
Network Bandwidth Yüksek Düşük %60-80 azalma
CPU Kullanımı Orta Düşük %30-50 azalma

12. Test Stratejileri

12.1 Unit Testing

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package main

import (
	"context"
	"testing"
	
	"github.com/stretchr/testify/assert"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	
	pb "example.com/user"
)

func TestGetUser(t *testing.T) {
	// Mock server oluştur
	s := &server{}
	
	tests := []struct {
		name    string
		req     *pb.UserRequest
		want    *pb.UserResponse
		wantErr codes.Code
	}{
		{
			name:    "valid user",
			req:     &pb.UserRequest{Id: 1},
			want:    mockUsers[0],
			wantErr: codes.OK,
		},
		{
			name:    "user not found",
			req:     &pb.UserRequest{Id: 999},
			want:    nil,
			wantErr: codes.NotFound,
		},
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := context.Background()
			got, err := s.GetUser(ctx, tt.req)
			
			if tt.wantErr != codes.OK {
				assert.Error(t, err)
				st, ok := status.FromError(err)
				assert.True(t, ok)
				assert.Equal(t, tt.wantErr, st.Code())
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.want.Id, got.Id)
			}
		})
	}
}

12.2 Integration Testing

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package main

import (
	"context"
	"net"
	"testing"
	
	"google.golang.org/grpc"
	"google.golang.org/grpc/test/bufconn"
	
	pb "example.com/user"
)

func TestGRPCIntegration(t *testing.T) {
	lis := bufconn.Listen(1024 * 1024)
	
	grpcServer := grpc.NewServer()
	pb.RegisterUserServiceServer(grpcServer, &server{})
	
	go func() {
		if err := grpcServer.Serve(lis); err != nil {
			t.Fatalf("Server failed: %v", err)
		}
	}()
	
	// Test client
	conn, err := grpc.Dial("bufnet",
		grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
			return lis.Dial()
		}),
		grpc.WithInsecure(),
	)
	if err != nil {
		t.Fatalf("Failed to dial: %v", err)
	}
	defer conn.Close()
	
	client := pb.NewUserServiceClient(conn)
	
	// Test çağrısı
	resp, err := client.GetUser(context.Background(), &pb.UserRequest{Id: 1})
	if err != nil {
		t.Fatalf("GetUser failed: %v", err)
	}
	
	if resp.Id != 1 {
		t.Errorf("Expected ID 1, got %d", resp.Id)
	}
}

13. Sonuç: Geleceğin İletişim Protokolü Bugünden Burada

Golang + gRPC kombinasyonu, modern dağıtık sistemler için adeta bir süper güç. Performans, tip güvenliği, ve geliştirici deneyimini bir araya getiren bu teknoloji yığını, mikroservis mimarilerinin en kritik bileşeni olan servisler arası iletişimi bir sanat eserine dönüştürüyor.

13.1 Anahtar Çıkarımlar

  1. Performans: HTTP/2 + Protobuf ile geleneksel REST’e göre katlanan performans
  2. Tip Güvenliği: .proto kontratları ile tutarlı, güvenli API’ler
  3. Esneklik: 4 farklı iletişim modeli ile her senaryoya uyum
  4. Ekosistem: Zengin tooling ve kütüphane desteği
  5. Production Ready: Auth, load balancing, health check, monitoring

13.2 Başlangıç Önerileri

  1. Prototipleme: Küçük bir internal servis ile başlayın
  2. Tooling: buf.build, grpcui, grpcurl gibi araçları öğrenin
  3. Monitoring: OpenTelemetry ile observability ekleyin
  4. Güvenlik: TLS ve JWT ile production hardening yapın
  5. Best Practices: Error handling, timeout, retry politikalarını uygulayın

13.3 Öğrenme Kaynakları

gRPC ve Go, dağıtık sistemlerin geleceğini şekillendiriyor. Bugün öğrenmeye başlayarak, yarının yüksek performanslı, ölçeklenebilir sistemlerine hazır olun!


Not: Bu makale teknik bir rehber olarak hazırlanmıştır. Üretim ortamında kullanmadan önce kapsamlı test yapılması önerilir.